@fjall/deploy-core 2.7.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 119 files minified at 2026-06-01T20:33:48.440Z
1
+ 120 files minified at 2026-06-02T01:05:50.743Z
@@ -1,5 +1,5 @@
1
- import{success as H,failure as P}from"@fjall/generator";import{OrganizationsClient as Pt}from"@aws-sdk/client-organizations";import{ORGANISATION_TYPES as W,getOrganisationStackName as dt}from"../types/operations.js";import{CdkContextBuilder as It}from"../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as Nt}from"../types/deployment/index.js";import{ensureOrganisationExists as wt}from"../aws/organisations/organisation.js";import{buildParamsContext as yt,collectStackOutputs as ut,synthOrFail as lt,bootstrapOrFail as pt,forwardOutput as mt,forwardResourceProgress as gt}from"./contextHelpers.js";import{partitionAccounts as ft,deployCascadeAccount as Ct,readPlatformIpamPoolIds as ht,deployDomains as Dt,buildRegionList as Tt,buildAccountRegionPairs as kt,CASCADE_MAX_CONCURRENCY as bt}from"./cascadeHelpers.js";import{projectScalarSummary as St}from"./cascadeSummary.js";import{reconcileProviderAccounts as Lt,mergeReconciledProviderAccounts as Mt}from"./reconcileProviderAccounts.js";import{maskSensitiveOutput as u,STRUCTURAL_ENVIRONMENTS as _t,mapSettledWithConcurrency as Gt}from"@fjall/util";import{INFRA_STEP_NAME as j,STEP_IDS as h,STEP_NAMES as J}from"../types/stepDefinitions.js";async function qt(e,o,r){const g=Date.now();switch(r.type){case W.ORGANISATION:return Yt(e,o,r,g);case W.PLATFORM:return Ot(e,o,r,"platform",g);case W.ACCOUNT:return Ot(e,o,r,"account",g);default:{const t=r.type;return P(new Error(`Unsupported organisation type: ${String(t)}`))}}}function At(e,o,r,g,t,a){return It.buildDeploymentContext({deployType:g,target:r.target,path:r.path,region:o.awsProvider.getRegion(),accountName:a,callerIdentity:Nt(o.awsProvider.getAccountId()),orgId:t.orgId,rootId:t.rootId,managementAccountId:t.managementAccountId,...yt({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc})},{verbose:e.options?.verbose,infraOnly:e.options?.infraOnly},e.orgConfig)}async function Et(e){const o=e.awsProvider.getClient(Pt),r=await wt(o);return r.success?H({orgId:r.data.orgId,rootId:r.data.rootId,managementAccountId:r.data.managementAccountId}):P(r.error)}const n={CONNECT:{id:h.CONNECT,name:j.CONNECT},PREPARE:{id:h.PREPARE_ENVIRONMENT,name:j.PREPARE},DEPLOY:{id:h.DEPLOY,name:j.DEPLOY},MONITORING:{id:h.MONITORING,name:j.MONITORING},ORG_DEPLOY:{id:h.ORG_DEPLOY,name:J.ORG_DEPLOY},CASCADE_PLATFORM:{id:h.CASCADE_PLATFORM,name:J.CASCADE_PLATFORM},CASCADE_ACCOUNTS:{id:h.CASCADE_ACCOUNTS,name:J.CASCADE_ACCOUNTS}},S=4;async function Ot(e,o,r,g,t){const{callbacks:a}=e;a.onStepComplete?.(n.CONNECT.id,n.CONNECT.name,"completed",0,S),a.onStepStart?.(n.PREPARE.id,n.PREPARE.name,1,S);const c=await Et(o);if(!c.success){a.onStepComplete?.(n.PREPARE.id,n.PREPARE.name,"error",1,S);const I=new Error(u(c.error.message));return a.onError?.(I),P(I)}const Y=At(e,o,r,g,c.data,g==="account"?r.target:void 0);a.onLog?.(`Synthesising ${g} infrastructure\u2026`,"info");const b=await lt(o,Y,a,"CDK synthesis failed");if(!b.success)return a.onStepComplete?.(n.PREPARE.id,n.PREPARE.name,"error",1,S),b;const L=await pt(o,Y,a);if(!L.success)return a.onStepComplete?.(n.PREPARE.id,n.PREPARE.name,"error",1,S),L;a.onStepComplete?.(n.PREPARE.id,n.PREPARE.name,"completed",1,S);const D=dt(r.type);a.onStepStart?.(n.DEPLOY.id,n.DEPLOY.name,2,S);const T=await o.cdkService.runCdkDeploy(Y,D,mt(a),gt(a),o.awsProvider);if(!T.success){a.onStepComplete?.(n.DEPLOY.id,n.DEPLOY.name,"error",2,S);const I=new Error(u(T.error));return a.onError?.(I),P(I)}a.onStepComplete?.(n.DEPLOY.id,n.DEPLOY.name,"completed",2,S);const k=await o.cfnService.getStackOutputs(D);k.success||a.onLog?.("Failed to read stack outputs (non-critical)","debug");const z=ut(k);return a.onStepStart?.(n.MONITORING.id,n.MONITORING.name,3,S),a.onStepComplete?.(n.MONITORING.id,n.MONITORING.name,"completed",3,S),H({target:r.target,deploymentType:"organisation",outputs:z,durationMs:Date.now()-t})}async function Yt(e,o,r,g){const{callbacks:t,options:a}=e;let c=e.orgConfig?.providerAccounts??[];if(c.length===0||c.every(s=>s.environment===_t.ROOT)){const s=await Lt(o,e.workingDirectory);if(s.success){const{providerAccounts:i,missingAccountNames:E}=s.data;i.length>0&&(c=Mt(e.orgConfig,i).providerAccounts,t.onLog?.(`Reconciled ${i.length} account(s) from AWS Organizations`,"info")),E.length>0&&(t.onCascadeMissingAccounts?.(E),t.onProgress?.({type:"warning",message:u(`Accounts declared in ACCOUNTS but not yet in AWS Organizations (cascade will skip): ${E.join(", ")}`)}))}else t.onProgress?.({type:"warning",message:u(`Could not reconcile accounts from AWS Organizations \u2014 cascade may skip accounts: ${s.error.message}`)})}const b=e.orgConfig?.primaryRegion??o.awsProvider.getRegion();c=c.map(s=>s.region!==void 0?s:{...s,region:b});const L=e.orgConfig!==void 0?{...e.orgConfig,providerAccounts:c}:c.length>0?{providerAccounts:c}:void 0,D=await Et(o);if(!D.success){const s=new Error(u(D.error.message));return t.onError?.(s),P(s)}const T=At(e,o,r,"organisation",D.data),k=a?.cascade!==!1,{platformAccount:z,memberAccounts:I}=ft(c),Q=k&&z!==void 0?1:0,Z=k&&I.length>0?1:0,l=2+Q+Z;t.onCascadeAccountsReconciled?.({hasPlatformAccount:Q>0,hasMemberAccounts:Z>0});const{id:$,name:F}=n.PREPARE;t.onStepStart?.($,F,0,l),t.onLog?.("Synthesising organisation infrastructure\u2026","info");const tt=await lt(o,T,t,"CDK synthesis failed");if(!tt.success)return t.onStepComplete?.($,F,"error",0,l),tt;const et=await pt(o,T,t);if(!et.success)return t.onStepComplete?.($,F,"error",0,l),et;t.onStepComplete?.($,F,"completed",0,l);const{id:K,name:V}=n.ORG_DEPLOY;t.onStepStart?.(K,V,1,l);const ot=dt(W.ORGANISATION),nt=await o.cdkService.runCdkDeploy(T,ot,mt(t),gt(t),o.awsProvider);if(!nt.success){t.onStepComplete?.(K,V,"error",1,l);const s=new Error(u(nt.error));return t.onError?.(s),P(s)}const rt=await o.cfnService.getStackOutputs(ot);rt.success||t.onLog?.("Failed to read org stack outputs (non-critical)","debug");const Rt=ut(rt);t.onStepComplete?.(K,V,"completed",1,l);const p=[],x=[];if(k&&c.length>0){t.onCascadeStart?.();const s=Date.now();let i=2,E=!1,U,at=!1;const v=[],st=d=>({members:v,...U!==void 0?{platform:U}:{},domainsDeployed:at,errors:p,totalDurationMs:d}),{platformAccount:A,memberAccounts:B}=ft(c);if(A){const{id:d,name:m}=n.CASCADE_PLATFORM;t.onStepStart?.(d,m,i,l),t.onCascadePhaseStart?.("platform");let f;const X=Date.now();try{f=await Ct(e,o,r,A,"platform",t,{orgConfig:L})}catch(O){const _=u(O instanceof Error?O.message:String(O));p.push({accountId:A.id,error:_}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(d,m,"error",i,l),f=P(new Error(_))}const M=Date.now()-X;if(f.success){E=!0;const O=f.data.skipped===!0;f.data.outputs&&x.push({accountId:A.id,outputs:f.data.outputs}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(d,m,O?"skipped":"completed",i,l),U={accountId:A.id,result:O?"skipped":"succeeded",durationMs:M}}else p.some(O=>O.accountId===A.id)||(p.push({accountId:A.id,error:u(f.error.message)}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(d,m,"error",i,l)),U={accountId:A.id,result:"failed",durationMs:M,error:u(f.error.message)};i++}let ct=new Map;if(E&&A&&(ct=await ht(o,A,t)),e.domainProvider){const d=await Dt(e.domainProvider,t);at=d.domainsDeployed>0;for(const m of d.errors)p.push({accountId:"domains",error:u(m)})}if(B.length>0){const{id:d,name:m}=n.CASCADE_ACCOUNTS;t.onStepStart?.(d,m,i,l),t.onCascadePhaseStart?.("accounts");const f=Tt(e.orgConfig),X=f[0]??b,M=kt(B,f);(await Gt(M,bt,async({account:R,region:G})=>{const N=G.replace(/-/g,""),C=ct.get(`${R.id}-${N}`),w=Date.now();return{result:await Ct(e,o,r,R,"account",t,{ipamPoolId:C,orgConfig:L,region:G,skipAccountGlobals:G!==X}),durationMs:Date.now()-w}})).forEach((R,G)=>{const N=M[G];if(!N)return;const C=N.account;if(R.status==="rejected"){const y=u(R.reason instanceof Error?R.reason.message:String(R.reason));v.push({accountId:C.id,accountName:C.name,region:N.region,result:"failed",durationMs:0,error:y}),p.push({accountId:C.id,error:y});return}const{result:w,durationMs:q}=R.value;if(w.success){const y=w.data.skipped===!0;w.data.outputs&&x.push({accountId:C.id,outputs:w.data.outputs}),v.push({accountId:C.id,accountName:C.name,region:N.region,result:y?"skipped":"succeeded",durationMs:q})}else{const y=u(w.error.message);v.push({accountId:C.id,accountName:C.name,region:N.region,result:"failed",durationMs:q,error:y}),p.push({accountId:C.id,error:y})}});const _=St(st(0));t.onCascadePhaseComplete?.("accounts"),t.onStepComplete?.(d,m,_.accountsFailed>0?"error":_.accountsSkipped===B.length?"skipped":"completed",i,l)}const it=st(Date.now()-s);if(t.onCascadeComplete?.(St(it)),t.onCascadeLedger?.(it),p.length>0){const d=p.map(m=>` ${m.accountId}: ${m.error}`).join(`
2
- `);t.onLog?.(u(`Cascade failed for ${p.length} target(s):
3
- ${d}`),"warn")}}if(p.length>0){const s=p.map(E=>u(`${E.accountId}: ${E.error}`)).join(`
4
- `),i=new Error(`Organisation root deployed, but the cascade failed for ${p.length} target(s):
5
- ${s}`);return t.onError?.(i),P(i)}return H({target:r.target,deploymentType:"organisation",outputs:Rt,...x.length>0?{cascadeOutputs:x}:{},durationMs:Date.now()-g})}export{qt as deployOrganisation};
1
+ import{join as It}from"node:path";import{success as ot,failure as y}from"@fjall/generator";import{OrganizationsClient as yt}from"@aws-sdk/client-organizations";import{ORGANISATION_TYPES as H,getOrganisationStackName as gt}from"../types/operations.js";import{CdkContextBuilder as wt}from"../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as Nt}from"../types/deployment/index.js";import{ensureOrganisationExists as Dt}from"../aws/organisations/organisation.js";import{buildParamsContext as Tt,collectStackOutputs as nt,synthOrFail as mt,bootstrapOrFail as ft,forwardOutput as Ct,forwardResourceProgress as St}from"./contextHelpers.js";import{partitionAccounts as Ot,deployCascadeAccount as At,readPlatformIpamPoolIds as kt,deployDomains as Lt,buildRegionList as bt,buildAccountRegionPairs as Mt,CASCADE_MAX_CONCURRENCY as _t}from"./cascadeHelpers.js";import{projectScalarSummary as Et}from"./cascadeSummary.js";import{reconcileProviderAccounts as $t,mergeReconciledProviderAccounts as Gt}from"./reconcileProviderAccounts.js";import{maskSensitiveOutput as i,STRUCTURAL_ENVIRONMENTS as Yt,mapSettledWithConcurrency as Ft}from"@fjall/util";import{INFRA_STEP_NAME as K,STEP_IDS as k,STEP_NAMES as rt}from"../types/stepDefinitions.js";async function Zt(o,e,a){const f=Date.now();switch(a.type){case H.ORGANISATION:return xt(o,e,a,f);case H.PLATFORM:return ht(o,e,a,"platform",f);case H.ACCOUNT:return ht(o,e,a,"account",f);default:{const t=a.type;return y(new Error(`Unsupported organisation type: ${String(t)}`))}}}function Rt(o,e,a,f,t,s){return wt.buildDeploymentContext({deployType:f,target:a.target,path:a.path,region:e.awsProvider.getRegion(),accountName:s,callerIdentity:Nt(e.awsProvider.getAccountId()),orgId:t.orgId,rootId:t.rootId,managementAccountId:t.managementAccountId,...Tt({orgConfig:o.orgConfig,identity:o.identity,skipOidc:o.options?.skipOidc})},{verbose:o.options?.verbose,infraOnly:o.options?.infraOnly},o.orgConfig)}async function Pt(o){const e=o.awsProvider.getClient(yt),a=await Dt(e);return a.success?ot({orgId:a.data.orgId,rootId:a.data.rootId,managementAccountId:a.data.managementAccountId}):y(a.error)}const r={CONNECT:{id:k.CONNECT,name:K.CONNECT},PREPARE:{id:k.PREPARE_ENVIRONMENT,name:K.PREPARE},DEPLOY:{id:k.DEPLOY,name:K.DEPLOY},MONITORING:{id:k.MONITORING,name:K.MONITORING},ORG_DEPLOY:{id:k.ORG_DEPLOY,name:rt.ORG_DEPLOY},CASCADE_PLATFORM:{id:k.CASCADE_PLATFORM,name:rt.CASCADE_PLATFORM},CASCADE_ACCOUNTS:{id:k.CASCADE_ACCOUNTS,name:rt.CASCADE_ACCOUNTS}},O=4;async function ht(o,e,a,f,t){const{callbacks:s}=o;s.onStepComplete?.(r.CONNECT.id,r.CONNECT.name,"completed",0,O),s.onStepStart?.(r.PREPARE.id,r.PREPARE.name,1,O);const d=await Pt(e);if(!d.success){s.onStepComplete?.(r.PREPARE.id,r.PREPARE.name,"error",1,O);const w=new Error(i(d.error.message));return s.onError?.(w),y(w)}const x=Rt(o,e,a,f,d.data,f==="account"?a.target:void 0);s.onLog?.(`Synthesising ${f} infrastructure\u2026`,"info");const M=await mt(e,x,s,"CDK synthesis failed");if(!M.success)return s.onStepComplete?.(r.PREPARE.id,r.PREPARE.name,"error",1,O),M;const _=await ft(e,x,s);if(!_.success)return s.onStepComplete?.(r.PREPARE.id,r.PREPARE.name,"error",1,O),_;s.onStepComplete?.(r.PREPARE.id,r.PREPARE.name,"completed",1,O);const L=gt(a.type);s.onStepStart?.(r.DEPLOY.id,r.DEPLOY.name,2,O);const P=await e.cdkService.runCdkDeploy(x,L,Ct(s),St(s),e.awsProvider);if(!P.success){s.onStepComplete?.(r.DEPLOY.id,r.DEPLOY.name,"error",2,O);const w=new Error(i(P.error));return s.onError?.(w),y(w)}s.onStepComplete?.(r.DEPLOY.id,r.DEPLOY.name,"completed",2,O);const b=await e.cfnService.getStackOutputs(L);b.success||s.onLog?.("Failed to read stack outputs (non-critical)","debug");const V=nt(b);return s.onStepStart?.(r.MONITORING.id,r.MONITORING.name,3,O),s.onStepComplete?.(r.MONITORING.id,r.MONITORING.name,"completed",3,O),ot({target:a.target,deploymentType:"organisation",outputs:V,durationMs:Date.now()-t})}async function xt(o,e,a,f){const{callbacks:t,options:s}=o;let d=o.orgConfig?.providerAccounts??[];if(d.length===0||d.every(n=>n.environment===Yt.ROOT)){const n=await $t(e,o.workingDirectory);if(n.success){const{providerAccounts:c,missingAccountNames:g}=n.data;c.length>0&&(d=Gt(o.orgConfig,c).providerAccounts,t.onLog?.(`Reconciled ${c.length} account(s) from AWS Organizations`,"info")),g.length>0&&(t.onCascadeMissingAccounts?.(g),t.onProgress?.({type:"warning",message:i(`Accounts declared in ACCOUNTS but not yet in AWS Organizations (cascade will skip): ${g.join(", ")}`)}))}else t.onProgress?.({type:"warning",message:i(`Could not reconcile accounts from AWS Organizations \u2014 cascade may skip accounts: ${n.error.message}`)})}const M=o.orgConfig?.primaryRegion??e.awsProvider.getRegion();d=d.map(n=>n.region!==void 0?n:{...n,region:M});const _=o.orgConfig!==void 0?{...o.orgConfig,providerAccounts:d}:d.length>0?{providerAccounts:d}:void 0,L=await Pt(e);if(!L.success){const n=new Error(i(L.error.message));return t.onError?.(n),y(n)}const P=Rt(o,e,a,"organisation",L.data),b=s?.cascade!==!1,{platformAccount:V,memberAccounts:w}=Ot(d),at=b&&V!==void 0?1:0,st=b&&w.length>0?1:0,l=2+at+st;t.onCascadeAccountsReconciled?.({hasPlatformAccount:at>0,hasMemberAccounts:st>0});const{id:U,name:v}=r.PREPARE;t.onStepStart?.(U,v,0,l),t.onLog?.("Synthesising organisation infrastructure\u2026","info");const ct=await mt(e,P,t,"CDK synthesis failed");if(!ct.success)return t.onStepComplete?.(U,v,"error",0,l),ct;const it=await ft(e,P,t);if(!it.success)return t.onStepComplete?.(U,v,"error",0,l),it;t.onStepComplete?.(U,v,"completed",0,l);const{id:B,name:X}=r.ORG_DEPLOY,N=gt(H.ORGANISATION);let dt=!0;const $=await e.hashService.getTemplateHashes(It(P.path,"cdk.out"));if($.success){const n=await e.hashService.compareWithState($.data,P.path);n.success?dt=n.data.stackChanges.get(N)??!0:t.onLog?.(i(`Org root change detection failed \u2014 deploying to be safe: ${n.error.message}`),"warn")}else t.onLog?.(i(`Org root template hashing failed \u2014 deploying to be safe: ${$.error.message}`),"warn");const q=dt||s?.force===!0||!await e.cfnService.stackExists(N);t.onOrgChangesDetected?.({hasOrgChanges:q});let J;if(q){t.onStepStart?.(B,X,1,l);const n=await e.cdkService.runCdkDeploy(P,N,Ct(t),St(t),e.awsProvider);if(!n.success){t.onStepComplete?.(B,X,"error",1,l);const A=new Error(i(n.error));return t.onError?.(A),y(A)}const c=await e.cfnService.getStackOutputs(N);c.success||t.onLog?.("Failed to read org stack outputs (non-critical)","debug"),J=nt(c);const g=$.success?$.data.get(N):void 0;if(g!==void 0){const A=await e.hashService.updateStateAfterDeploy(P.path,new Map([[N,g]]));A.success||t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy the org root: ${i(A.error.message)}`,"warn")}t.onStepComplete?.(B,X,"completed",1,l)}else{t.onLog?.("Organisation root: no infrastructure changes \u2014 skipping deploy","info");const n=await e.cfnService.getStackOutputs(N);n.success||t.onLog?.("Failed to read org stack outputs (non-critical)","debug"),J=nt(n)}const p=[],W=[];let j=q;if(b&&d.length>0){t.onCascadeStart?.();const n=Date.now();let c=2,g=!1,A,Q=!1;const z=[],ut=u=>({members:z,...A!==void 0?{platform:A}:{},domainsDeployed:Q,errors:p,totalDurationMs:u}),{platformAccount:E,memberAccounts:Z}=Ot(d);if(E){const{id:u,name:m}=r.CASCADE_PLATFORM;t.onStepStart?.(u,m,c,l),t.onCascadePhaseStart?.("platform");let C;const tt=Date.now();try{C=await At(o,e,a,E,"platform",t,{orgConfig:_})}catch(R){const Y=i(R instanceof Error?R.message:String(R));p.push({accountId:E.id,error:Y}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(u,m,"error",c,l),C=y(new Error(Y))}const G=Date.now()-tt;if(C.success){g=!0;const R=C.data.skipped===!0;R||(j=!0),C.data.outputs&&W.push({accountId:E.id,outputs:C.data.outputs}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(u,m,R?"skipped":"completed",c,l),A={accountId:E.id,result:R?"skipped":"succeeded",durationMs:G}}else p.some(R=>R.accountId===E.id)||(p.push({accountId:E.id,error:i(C.error.message)}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(u,m,"error",c,l)),A={accountId:E.id,result:"failed",durationMs:G,error:i(C.error.message)};c++}let lt=new Map;if(g&&E&&(lt=await kt(e,E,t)),o.domainProvider){const u=await Lt(o.domainProvider,t);Q=u.domainsDeployed>0,Q&&(j=!0);for(const m of u.errors)p.push({accountId:"domains",error:i(m)})}if(Z.length>0){const{id:u,name:m}=r.CASCADE_ACCOUNTS;t.onStepStart?.(u,m,c,l),t.onCascadePhaseStart?.("accounts");const C=bt(o.orgConfig),tt=C[0]??M,G=Mt(Z,C);(await Ft(G,_t,async({account:h,region:F})=>{const D=F.replace(/-/g,""),S=lt.get(`${h.id}-${D}`),T=Date.now();return{result:await At(o,e,a,h,"account",t,{ipamPoolId:S,orgConfig:_,region:F,skipAccountGlobals:F!==tt}),durationMs:Date.now()-T}})).forEach((h,F)=>{const D=G[F];if(!D)return;const S=D.account;if(h.status==="rejected"){const I=i(h.reason instanceof Error?h.reason.message:String(h.reason));z.push({accountId:S.id,accountName:S.name,region:D.region,result:"failed",durationMs:0,error:I}),p.push({accountId:S.id,error:I});return}const{result:T,durationMs:et}=h.value;if(T.success){const I=T.data.skipped===!0;I||(j=!0),T.data.outputs&&W.push({accountId:S.id,outputs:T.data.outputs}),z.push({accountId:S.id,accountName:S.name,region:D.region,result:I?"skipped":"succeeded",durationMs:et})}else{const I=i(T.error.message);z.push({accountId:S.id,accountName:S.name,region:D.region,result:"failed",durationMs:et,error:I}),p.push({accountId:S.id,error:I})}});const Y=Et(ut(0));t.onCascadePhaseComplete?.("accounts"),t.onStepComplete?.(u,m,Y.accountsFailed>0?"error":Y.accountsSkipped===Z.length?"skipped":"completed",c,l)}const pt=ut(Date.now()-n);if(t.onCascadeComplete?.(Et(pt)),t.onCascadeLedger?.(pt),p.length>0){const u=p.map(m=>` ${m.accountId}: ${m.error}`).join(`
2
+ `);t.onLog?.(i(`Cascade failed for ${p.length} target(s):
3
+ ${u}`),"warn")}}if(p.length>0){const n=p.map(g=>i(`${g.accountId}: ${g.error}`)).join(`
4
+ `),c=new Error(`Organisation root deployed, but the cascade failed for ${p.length} target(s):
5
+ ${n}`);return t.onError?.(c),y(c)}return ot({target:a.target,deploymentType:"organisation",outputs:J,...W.length>0?{cascadeOutputs:W}:{},...j?{}:{noChanges:!0},durationMs:Date.now()-f})}export{Zt as deployOrganisation};
@@ -1 +1 @@
1
- import{APPLICATION_STACKS as n,APPLICATION_DEPLOY_ORDER as A,getApplicationStepName as t,getApplicationStepId as a}from"../types/index.js";import{STEP_IDS as e,STEP_NAMES as s,INFRA_STEP_NAME as d}from"../types/index.js";const O={Network:e.NETWORK_DESTROY,Storage:e.STORAGE_DESTROY,Messaging:e.MESSAGING_DESTROY,Database:e.DATABASE_DESTROY,Compute:e.COMPUTE_DESTROY,Cdn:e.CDN_DESTROY};function C(f){return O[f]??`${f.toLowerCase()}-destroy`}class p{static DEPLOYMENT_STEPS=new Map([["application-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.PREPARE_DEPLOY},{id:e.BOOTSTRAP,name:s.BOOTSTRAP,conditions:{requiresInfra:!0}},{id:a(n.NETWORK,"deploy"),name:t(n.NETWORK,"deploy"),conditions:{requiresInfra:!0,requiresNetwork:!0}},{id:a(n.STORAGE,"deploy"),name:t(n.STORAGE,"deploy"),conditions:{requiresInfra:!0,requiresStorage:!0}},{id:a(n.MESSAGING,"deploy"),name:t(n.MESSAGING,"deploy"),conditions:{requiresInfra:!0,requiresMessaging:!0}},{id:a(n.DATABASE,"deploy"),name:t(n.DATABASE,"deploy"),conditions:{requiresInfra:!0,requiresDatabase:!0}},{id:e.DOCKER_OPERATIONS,name:"Docker operations",conditions:{requiresDocker:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:e.TAG_ECR_IMAGES,name:"Tagging container images",conditions:{requiresImageTagging:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:a(n.COMPUTE,"deploy"),name:t(n.COMPUTE,"deploy"),conditions:{requiresInfra:!0,requiresCompute:!0}},{id:a(n.CDN,"deploy"),name:t(n.CDN,"deploy"),conditions:{requiresInfra:!0,requiresCdn:!0}}]],["application-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.CHECK_INFRA_STATE},{id:a(n.CDN,"destroy"),name:t(n.CDN,"destroy"),conditions:{requiresCdn:!0}},{id:a(n.COMPUTE,"destroy"),name:t(n.COMPUTE,"destroy")},{id:a(n.DATABASE,"destroy"),name:t(n.DATABASE,"destroy")},{id:a(n.MESSAGING,"destroy"),name:t(n.MESSAGING,"destroy"),conditions:{requiresMessaging:!0}},{id:a(n.STORAGE,"destroy"),name:t(n.STORAGE,"destroy"),conditions:{requiresStorage:!0}},{id:a(n.NETWORK,"destroy"),name:t(n.NETWORK,"destroy")}]],["organisation-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.ORG_SETUP,name:s.ORG_SETUP},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.ORG_DEPLOY,name:s.ORG_DEPLOY},{id:e.CASCADE_PLATFORM,name:s.CASCADE_PLATFORM,conditions:{requiresPlatformAccount:!0}},{id:e.CASCADE_ACCOUNTS,name:s.CASCADE_ACCOUNTS,conditions:{requiresMemberAccounts:!0}}]],["organisation-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:s.CHECK_INFRA_STATE},{id:e.CASCADE_ACCOUNTS,name:"Destroying account infrastructure",conditions:{requiresMemberAccounts:!0}},{id:e.CASCADE_PLATFORM,name:"Destroying platform infrastructure",conditions:{requiresPlatformAccount:!0}},{id:e.DESTROY,name:"Destroying organisation infrastructure"}]],["platform-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:d.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.DEPLOY,name:d.DEPLOY},{id:e.MONITORING,name:d.MONITORING}]],["platform-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying platform infrastructure"}]],["account-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:d.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.DEPLOY,name:d.DEPLOY},{id:e.MONITORING,name:d.MONITORING}]],["account-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying account infrastructure"}]]]);static getSteps(r){if(r.deploymentType==="application"&&r.operation==="deploy"&&r.deployOnly)return[{id:e.AUTH,name:s.AUTH},{id:e.DOCKER_OPERATIONS,name:"Build and push Docker container"},{id:a(n.COMPUTE,"deploy"),name:t(n.COMPUTE,"deploy")}];const o=`${r.deploymentType}-${r.operation}`;return(this.DEPLOYMENT_STEPS.get(o)??[]).filter(i=>{if(r.deploymentType==="application"){const l=r.builderName==="opennext";if(i.conditions?.requiresInfra&&r.deployOnly)return!1;if(r.resources){const c=r.resources;if(i.conditions?.requiresNetwork&&!c.hasNetwork||i.conditions?.requiresCompute&&!c.hasCompute||i.conditions?.requiresDatabase&&!c.hasDatabase||i.conditions?.requiresStorage&&!c.hasStorage||i.conditions?.requiresMessaging&&!c.hasMessaging||i.conditions?.requiresCdn&&!c.hasCdn)return!1}else if(r.operation==="deploy"&&!l&&(i.conditions?.requiresNetwork||i.conditions?.requiresCompute||i.conditions?.requiresDatabase||i.conditions?.requiresStorage||i.conditions?.requiresMessaging||i.conditions?.requiresCdn))return!1;if(i.conditions?.requiresDocker||i.id===e.DOCKER_OPERATIONS)return l||r.infraOnly||r.resources&&!r.resources.hasCompute?!1:r.deployOnly?r.hasDockerfile===!0:r.hasDockerfile===!0||r.hasDockerfile===!1&&!r.isManagedAccount;if(i.conditions?.requiresImageTagging)return r.infraOnly||l||r.resources&&!r.resources.hasCompute?!1:r.hasDockerfile===!1}return!(i.conditions?.requiresMemberAccounts&&!r.hasMemberAccounts||i.conditions?.requiresPlatformAccount&&!r.hasPlatformAccount||i.conditions?.requiresOrgChanges&&r.hasOrgChanges!==!0||i.conditions?.requiresPlatformChanges&&r.hasPlatformChanges!==!0||i.conditions?.requiresAccountChanges&&r.hasAccountChanges!==!0||i.conditions?.requiresDomainConfiguration&&!r.hasDomainConfiguration||i.conditions?.requiresDomainChanges&&r.hasDomainChanges!==!0)}).map(i=>i.id===e.DOCKER_OPERATIONS?{...i,name:r.hasDockerfile?"Building and pushing Docker image":"Initialising container repository"}:i)}static getStepNames(r){return this.getSteps(r).map(o=>o.name)}static getStepIndex(r,o){return this.getSteps(o).findIndex(E=>E.id===r)}static getStepById(r,o="application"){const u=`${o}-deploy`,i=(this.DEPLOYMENT_STEPS.get(u)||[]).find(T=>T.id===r);if(i)return i;const l=`${o}-destroy`;return(this.DEPLOYMENT_STEPS.get(l)||[]).find(T=>T.id===r)}static isStepIncluded(r,o){return this.getSteps(o).some(E=>E.id===r)}static createContext(r,o,u){return{deploymentType:r,operation:o,deployOnly:u?.deployOnly??!1,infraOnly:u?.infraOnly??!1,isManagedAccount:u?.isManagedAccount??!1,hasDockerfile:u?.hasDockerfile??!1,pattern:u?.pattern??null,builderName:u?.builderName,resources:u?.resources}}static getDeploymentTypes(){return["application","organisation","platform","account"]}static getStackNames(r){switch(r){case"application":return[...A];case"organisation":return["Organisation"];case"platform":return["Platform"];case"account":return["Account"];default:return[]}}static getInfraStepIds(){return[e.CFN_CHECK,e.BOOTSTRAP,e.DIFF,e.NETWORK,e.STORAGE,e.MESSAGING,e.DATABASE,e.COMPUTE,e.CDN]}static getDockerStepIds(){return[e.ECR_INIT,e.DOCKER_DEPLOY]}}export{p as StepRegistry,C as getDestroyStepId};
1
+ import{APPLICATION_STACKS as n,APPLICATION_DEPLOY_ORDER as A,getApplicationStepName as t,getApplicationStepId as a}from"../types/index.js";import{STEP_IDS as e,STEP_NAMES as s,INFRA_STEP_NAME as d}from"../types/index.js";const O={Network:e.NETWORK_DESTROY,Storage:e.STORAGE_DESTROY,Messaging:e.MESSAGING_DESTROY,Database:e.DATABASE_DESTROY,Compute:e.COMPUTE_DESTROY,Cdn:e.CDN_DESTROY};function S(f){return O[f]??`${f.toLowerCase()}-destroy`}class p{static DEPLOYMENT_STEPS=new Map([["application-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.PREPARE_DEPLOY},{id:e.BOOTSTRAP,name:s.BOOTSTRAP,conditions:{requiresInfra:!0}},{id:a(n.NETWORK,"deploy"),name:t(n.NETWORK,"deploy"),conditions:{requiresInfra:!0,requiresNetwork:!0}},{id:a(n.STORAGE,"deploy"),name:t(n.STORAGE,"deploy"),conditions:{requiresInfra:!0,requiresStorage:!0}},{id:a(n.MESSAGING,"deploy"),name:t(n.MESSAGING,"deploy"),conditions:{requiresInfra:!0,requiresMessaging:!0}},{id:a(n.DATABASE,"deploy"),name:t(n.DATABASE,"deploy"),conditions:{requiresInfra:!0,requiresDatabase:!0}},{id:e.DOCKER_OPERATIONS,name:"Docker operations",conditions:{requiresDocker:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:e.TAG_ECR_IMAGES,name:"Tagging container images",conditions:{requiresImageTagging:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:a(n.COMPUTE,"deploy"),name:t(n.COMPUTE,"deploy"),conditions:{requiresInfra:!0,requiresCompute:!0}},{id:a(n.CDN,"deploy"),name:t(n.CDN,"deploy"),conditions:{requiresInfra:!0,requiresCdn:!0}}]],["application-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.CHECK_INFRA_STATE},{id:a(n.CDN,"destroy"),name:t(n.CDN,"destroy"),conditions:{requiresCdn:!0}},{id:a(n.COMPUTE,"destroy"),name:t(n.COMPUTE,"destroy")},{id:a(n.DATABASE,"destroy"),name:t(n.DATABASE,"destroy")},{id:a(n.MESSAGING,"destroy"),name:t(n.MESSAGING,"destroy"),conditions:{requiresMessaging:!0}},{id:a(n.STORAGE,"destroy"),name:t(n.STORAGE,"destroy"),conditions:{requiresStorage:!0}},{id:a(n.NETWORK,"destroy"),name:t(n.NETWORK,"destroy")}]],["organisation-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.ORG_SETUP,name:s.ORG_SETUP},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.ORG_DEPLOY,name:s.ORG_DEPLOY,conditions:{requiresOrgChanges:!0}},{id:e.CASCADE_PLATFORM,name:s.CASCADE_PLATFORM,conditions:{requiresPlatformAccount:!0}},{id:e.CASCADE_ACCOUNTS,name:s.CASCADE_ACCOUNTS,conditions:{requiresMemberAccounts:!0}}]],["organisation-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:s.CHECK_INFRA_STATE},{id:e.CASCADE_ACCOUNTS,name:"Destroying account infrastructure",conditions:{requiresMemberAccounts:!0}},{id:e.CASCADE_PLATFORM,name:"Destroying platform infrastructure",conditions:{requiresPlatformAccount:!0}},{id:e.DESTROY,name:"Destroying organisation infrastructure"}]],["platform-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:d.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.DEPLOY,name:d.DEPLOY},{id:e.MONITORING,name:d.MONITORING}]],["platform-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying platform infrastructure"}]],["account-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:d.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.DEPLOY,name:d.DEPLOY},{id:e.MONITORING,name:d.MONITORING}]],["account-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying account infrastructure"}]]]);static getSteps(r){if(r.deploymentType==="application"&&r.operation==="deploy"&&r.deployOnly)return[{id:e.AUTH,name:s.AUTH},{id:e.DOCKER_OPERATIONS,name:"Build and push Docker container"},{id:a(n.COMPUTE,"deploy"),name:t(n.COMPUTE,"deploy")}];const o=`${r.deploymentType}-${r.operation}`;return(this.DEPLOYMENT_STEPS.get(o)??[]).filter(i=>{if(r.deploymentType==="application"){const l=r.builderName==="opennext";if(i.conditions?.requiresInfra&&r.deployOnly)return!1;if(r.resources){const c=r.resources;if(i.conditions?.requiresNetwork&&!c.hasNetwork||i.conditions?.requiresCompute&&!c.hasCompute||i.conditions?.requiresDatabase&&!c.hasDatabase||i.conditions?.requiresStorage&&!c.hasStorage||i.conditions?.requiresMessaging&&!c.hasMessaging||i.conditions?.requiresCdn&&!c.hasCdn)return!1}else if(r.operation==="deploy"&&!l&&(i.conditions?.requiresNetwork||i.conditions?.requiresCompute||i.conditions?.requiresDatabase||i.conditions?.requiresStorage||i.conditions?.requiresMessaging||i.conditions?.requiresCdn))return!1;if(i.conditions?.requiresDocker||i.id===e.DOCKER_OPERATIONS)return l||r.infraOnly||r.resources&&!r.resources.hasCompute?!1:r.deployOnly?r.hasDockerfile===!0:r.hasDockerfile===!0||r.hasDockerfile===!1&&!r.isManagedAccount;if(i.conditions?.requiresImageTagging)return r.infraOnly||l||r.resources&&!r.resources.hasCompute?!1:r.hasDockerfile===!1}return!(i.conditions?.requiresMemberAccounts&&!r.hasMemberAccounts||i.conditions?.requiresPlatformAccount&&!r.hasPlatformAccount||i.conditions?.requiresOrgChanges&&r.hasOrgChanges!==!0||i.conditions?.requiresPlatformChanges&&r.hasPlatformChanges!==!0||i.conditions?.requiresAccountChanges&&r.hasAccountChanges!==!0||i.conditions?.requiresDomainConfiguration&&!r.hasDomainConfiguration||i.conditions?.requiresDomainChanges&&r.hasDomainChanges!==!0)}).map(i=>i.id===e.DOCKER_OPERATIONS?{...i,name:r.hasDockerfile?"Building and pushing Docker image":"Initialising container repository"}:i)}static getStepNames(r){return this.getSteps(r).map(o=>o.name)}static getStepIndex(r,o){return this.getSteps(o).findIndex(E=>E.id===r)}static getStepById(r,o="application"){const u=`${o}-deploy`,i=(this.DEPLOYMENT_STEPS.get(u)||[]).find(T=>T.id===r);if(i)return i;const l=`${o}-destroy`;return(this.DEPLOYMENT_STEPS.get(l)||[]).find(T=>T.id===r)}static isStepIncluded(r,o){return this.getSteps(o).some(E=>E.id===r)}static createContext(r,o,u){return{deploymentType:r,operation:o,deployOnly:u?.deployOnly??!1,infraOnly:u?.infraOnly??!1,isManagedAccount:u?.isManagedAccount??!1,hasDockerfile:u?.hasDockerfile??!1,pattern:u?.pattern??null,builderName:u?.builderName,resources:u?.resources}}static getDeploymentTypes(){return["application","organisation","platform","account"]}static getStackNames(r){switch(r){case"application":return[...A];case"organisation":return["Organisation"];case"platform":return["Platform"];case"account":return["Account"];default:return[]}}static getInfraStepIds(){return[e.CFN_CHECK,e.BOOTSTRAP,e.DIFF,e.NETWORK,e.STORAGE,e.MESSAGING,e.DATABASE,e.COMPUTE,e.CDN]}static getDockerStepIds(){return[e.ECR_INIT,e.DOCKER_DEPLOY]}}export{p as StepRegistry,S as getDestroyStepId};
@@ -0,0 +1,7 @@
1
+ import type { DeployCallbacks } from "./callbacks.js";
2
+ /**
3
+ * Every {@link DeployCallbacks} key (43 callbacks + the `verbose` flag),
4
+ * derived from the type-checked {@link CALLBACK_KEY_PRESENCE} map so the list
5
+ * can never silently diverge from the interface.
6
+ */
7
+ export declare const ALL_CALLBACK_KEYS: Array<keyof DeployCallbacks>;
@@ -0,0 +1 @@
1
+ const e={onStepStart:!0,onStepComplete:!0,onProgress:!0,onLog:!0,onOutput:!0,onResourceProgress:!0,onParallelPhaseStart:!0,onParallelPhaseComplete:!0,onParallelStackResourceProgress:!0,onDockerProgress:!0,onBuildPushStart:!0,onBuildPushProgress:!0,onBuildPushComplete:!0,onTaskDefRegistered:!0,onECSUpdate:!0,onECSProgress:!0,onECSComplete:!0,onMigrationsStart:!0,onMigrationsComplete:!0,onCDKBootstrap:!0,onCdkOutput:!0,onCascadeStart:!0,onCascadeAccountsReconciled:!0,onOrgChangesDetected:!0,onCascadeMissingAccounts:!0,onCascadePhaseStart:!0,onCascadePhaseComplete:!0,onCascadeAccountStart:!0,onCascadeAccountPhaseChange:!0,onCascadeAccountResourceProgress:!0,onCascadeAccountComplete:!0,onCascadeComplete:!0,onCascadeLedger:!0,onStackCleanupProgress:!0,onDetectionComplete:!0,onDetectionPhaseChange:!0,onSSOUrl:!0,onAuthComplete:!0,onOpenNextBuildStart:!0,onOpenNextProgress:!0,onOpenNextBuildComplete:!0,onOpenNextBuildError:!0,onError:!0,verbose:!0},t=Object.keys(e);export{t as ALL_CALLBACK_KEYS};
@@ -114,6 +114,18 @@ export interface DeployCallbacks {
114
114
  hasPlatformAccount: boolean;
115
115
  hasMemberAccounts: boolean;
116
116
  }) => void;
117
+ /**
118
+ * @emittedBy engine — fired once after org synthesis, before the org root
119
+ * deploy, carrying the template-hash verdict for the Organisation root stack.
120
+ * Consumers that pre-declare a fixed step list (the CLI Ink UI) use it to
121
+ * reveal the "Deploying organisation" pill, which is hidden at mount until the
122
+ * engine confirms a change. `hasOrgChanges` is false on a clean re-run (the
123
+ * root is skipped) and true when the root will deploy (changed, forced, or the
124
+ * stack is absent).
125
+ */
126
+ onOrgChangesDetected?: (info: {
127
+ hasOrgChanges: boolean;
128
+ }) => void;
117
129
  /**
118
130
  * @emittedBy engine — fired once after account reconciliation when accounts
119
131
  * are declared in ACCOUNTS but not yet present in AWS Organizations (the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/deploy-core",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "description": "Shared deployment engine for Fjall — used by CLI and webapp worker",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -73,8 +73,8 @@
73
73
  "@aws-sdk/client-s3": "^3.1038.0",
74
74
  "@aws-sdk/client-sso-admin": "^3.1038.0",
75
75
  "@aws-sdk/client-sts": "^3.1038.0",
76
- "@fjall/generator": "^2.7.1",
77
- "@fjall/util": "^2.7.1",
76
+ "@fjall/generator": "^2.8.0",
77
+ "@fjall/util": "^2.8.0",
78
78
  "@smithy/node-http-handler": "^4.6.1",
79
79
  "tsx": "^4.21.0",
80
80
  "zod": "^4.4.3"
@@ -83,5 +83,5 @@
83
83
  "@types/node": "^25.6.0",
84
84
  "vitest": "^4.1.5"
85
85
  },
86
- "gitHead": "2b37679546b7695b1678148e0b8e1f349afac3d9"
86
+ "gitHead": "6f82ea5b7fa9a4b23532708c5646a68e940c0b75"
87
87
  }