@fjall/deploy-core 2.19.3 → 2.19.5

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.
Files changed (86) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/organisations/accountGlobals.d.ts +1 -1
  3. package/dist/src/aws/targetReadiness.d.ts +1 -1
  4. package/dist/src/aws/targetSetAdvisory.d.ts +1 -1
  5. package/dist/src/orchestration/{applicationDeploy.d.ts → application/applicationDeploy.d.ts} +3 -3
  6. package/dist/src/orchestration/application/applicationDeploy.js +1 -0
  7. package/dist/src/orchestration/{applicationDeployHelpers.d.ts → application/applicationDeployHelpers.d.ts} +6 -6
  8. package/dist/src/orchestration/{applicationDeployHelpers.js → application/applicationDeployHelpers.js} +1 -1
  9. package/dist/src/orchestration/{applicationDestroy.d.ts → application/applicationDestroy.d.ts} +3 -3
  10. package/dist/src/orchestration/application/applicationDestroy.js +1 -0
  11. package/dist/src/orchestration/{codeOnlyDeploy.d.ts → application/codeOnlyDeploy.d.ts} +3 -3
  12. package/dist/src/orchestration/application/codeOnlyDeploy.js +1 -0
  13. package/dist/src/orchestration/{detectionPipeline.d.ts → application/detectionPipeline.d.ts} +5 -5
  14. package/dist/src/orchestration/application/detectionPipeline.js +1 -0
  15. package/dist/src/orchestration/{dockerBuildHelper.d.ts → application/dockerBuildHelper.d.ts} +4 -4
  16. package/dist/src/orchestration/application/dockerBuildHelper.js +1 -0
  17. package/dist/src/orchestration/{openNextBuild.d.ts → application/openNextBuild.d.ts} +3 -3
  18. package/dist/src/orchestration/{openNextBuild.js → application/openNextBuild.js} +1 -1
  19. package/dist/src/orchestration/{welcomeImageHelper.d.ts → application/welcomeImageHelper.d.ts} +4 -4
  20. package/dist/src/orchestration/application/welcomeImageHelper.js +1 -0
  21. package/dist/src/orchestration/builders/openNextBuilder.js +1 -1
  22. package/dist/src/orchestration/contextHelpers.d.ts +1 -1
  23. package/dist/src/orchestration/deploy.js +1 -1
  24. package/dist/src/orchestration/destroy.js +1 -1
  25. package/dist/src/orchestration/index.d.ts +15 -15
  26. package/dist/src/orchestration/index.js +1 -1
  27. package/dist/src/orchestration/{accountsConfig.d.ts → organisation/accountsConfig.d.ts} +2 -2
  28. package/dist/src/orchestration/organisation/accountsConfig.js +1 -0
  29. package/dist/src/orchestration/{cascadeDestroyHelpers.d.ts → organisation/cascadeDestroyHelpers.d.ts} +4 -4
  30. package/dist/src/orchestration/organisation/cascadeDestroyHelpers.js +1 -0
  31. package/dist/src/orchestration/{cascadeHelpers.d.ts → organisation/cascadeHelpers.d.ts} +7 -7
  32. package/dist/src/orchestration/organisation/cascadeHelpers.js +1 -0
  33. package/dist/src/orchestration/{cascadeSummary.d.ts → organisation/cascadeSummary.d.ts} +1 -1
  34. package/dist/src/orchestration/{organisationDeploy.d.ts → organisation/organisationDeploy.d.ts} +4 -4
  35. package/dist/src/orchestration/organisation/organisationDeploy.js +1 -0
  36. package/dist/src/orchestration/{organisationDestroy.d.ts → organisation/organisationDestroy.d.ts} +3 -3
  37. package/dist/src/orchestration/organisation/organisationDestroy.js +3 -0
  38. package/dist/src/orchestration/{organisationSetup.d.ts → organisation/organisationSetup.d.ts} +2 -2
  39. package/dist/src/orchestration/organisation/organisationSetup.js +1 -0
  40. package/dist/src/orchestration/{reconcileProviderAccounts.d.ts → organisation/reconcileProviderAccounts.d.ts} +2 -2
  41. package/dist/src/orchestration/organisation/reconcileProviderAccounts.js +1 -0
  42. package/dist/src/orchestration/organisationDeploy/cascadeExecution.d.ts +1 -1
  43. package/dist/src/orchestration/organisationDeploy/cascadeExecution.js +1 -1
  44. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +1 -1
  45. package/dist/src/orchestration/organisationDeploy/resolveCascadeAccounts.d.ts +1 -1
  46. package/dist/src/orchestration/organisationDeploy/resolveCascadeAccounts.js +1 -1
  47. package/dist/src/services/application/ApplicationStackService.d.ts +1 -1
  48. package/dist/src/services/application/applicationStackHelpers.d.ts +1 -1
  49. package/dist/src/services/supporting/CdkContextBuilder.d.ts +1 -1
  50. package/dist/src/services/supporting/TemplateHashService.js +1 -1
  51. package/dist/src/types/callbacks.d.ts +2 -2
  52. package/dist/src/types/detection/patternDetection.js +1 -0
  53. package/dist/src/types/frameworkBuilder.d.ts +1 -1
  54. package/dist/src/types/index.d.ts +8 -8
  55. package/dist/src/types/index.js +1 -1
  56. package/dist/src/types/operations.d.ts +1 -1
  57. package/dist/src/types/operations.js +1 -1
  58. package/dist/src/types/params.d.ts +1 -1
  59. package/dist/src/types/stepDefinitions.d.ts +1 -1
  60. package/package.json +5 -4
  61. package/dist/src/orchestration/accountsConfig.js +0 -1
  62. package/dist/src/orchestration/applicationDeploy.js +0 -1
  63. package/dist/src/orchestration/applicationDestroy.js +0 -1
  64. package/dist/src/orchestration/cascadeDestroyHelpers.js +0 -1
  65. package/dist/src/orchestration/cascadeHelpers.js +0 -1
  66. package/dist/src/orchestration/codeOnlyDeploy.js +0 -1
  67. package/dist/src/orchestration/detectionPipeline.js +0 -1
  68. package/dist/src/orchestration/dockerBuildHelper.js +0 -1
  69. package/dist/src/orchestration/organisationDeploy.js +0 -1
  70. package/dist/src/orchestration/organisationDestroy.js +0 -3
  71. package/dist/src/orchestration/organisationSetup.js +0 -1
  72. package/dist/src/orchestration/reconcileProviderAccounts.js +0 -1
  73. package/dist/src/orchestration/welcomeImageHelper.js +0 -1
  74. package/dist/src/types/patternDetection.js +0 -1
  75. /package/dist/src/orchestration/{cascadeSummary.js → organisation/cascadeSummary.js} +0 -0
  76. /package/dist/src/types/{FjallState.d.ts → config/FjallState.d.ts} +0 -0
  77. /package/dist/src/types/{FjallState.js → config/FjallState.js} +0 -0
  78. /package/dist/src/types/{entitlements.d.ts → config/entitlements.d.ts} +0 -0
  79. /package/dist/src/types/{entitlements.js → config/entitlements.js} +0 -0
  80. /package/dist/src/types/{orgConfig.d.ts → config/orgConfig.d.ts} +0 -0
  81. /package/dist/src/types/{orgConfig.js → config/orgConfig.js} +0 -0
  82. /package/dist/src/types/{detection.d.ts → detection/detection.d.ts} +0 -0
  83. /package/dist/src/types/{detection.js → detection/detection.js} +0 -0
  84. /package/dist/src/types/{patternDetection.d.ts → detection/patternDetection.d.ts} +0 -0
  85. /package/dist/src/types/{patternTypes.d.ts → detection/patternTypes.d.ts} +0 -0
  86. /package/dist/src/types/{patternTypes.js → detection/patternTypes.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { type Result } from "@fjall/generator";
2
2
  import type { ProviderAccount } from "@fjall/util/config";
3
- import type { OrgConfig } from "../types/orgConfig.js";
4
- import type { DeployServices } from "./serviceFactory.js";
3
+ import type { OrgConfig } from "../../types/config/orgConfig.js";
4
+ import type { DeployServices } from "../serviceFactory.js";
5
5
  export interface ReconcileResult {
6
6
  providerAccounts: ProviderAccount[];
7
7
  /** Account names declared in ACCOUNTS but not yet present in AWS Organizations. */
@@ -0,0 +1 @@
1
+ import{z as u}from"zod";import{success as T,failure as i}from"@fjall/generator";import{environmentToTier as v,stageFromWireEnvironment as y,normaliseError as g}from"@fjall/util";import{OrganizationsClient as E}from"@aws-sdk/client-organizations";import{listAccounts as P}from"../../aws/organisations/accounts.js";import{parseAccountsConfiguration as S,flattenAccountsToEnvironments as h}from"./accountsConfig.js";const w=6e4,O=u.object({Id:u.string().min(1),Name:u.string().min(1)});async function b(s,c){let n;const r=new Promise((o,e)=>{n=setTimeout(()=>e(new Error(`Timed out parsing ACCOUNTS configuration after ${w}ms`)),w)});let t;try{t=await Promise.race([S(c),r])}catch(o){return i(g(o))}finally{n!==void 0&&clearTimeout(n)}if(!t.success)return i(new Error(`Failed to parse ACCOUNTS configuration: ${t.error.message}`));if(t.data===null)return i(new Error("ACCOUNTS configuration file not found"));const C=h(t.data),f=new Map;for(const{accountName:o,environment:e}of C)f.set(o.toLowerCase(),{environment:e,displayName:o});let d;try{d=s.awsProvider.getClient(E)}catch(o){return i(g(o))}const a=await P(d);if(!a.success)return i(a.error);const l=new Map;for(const o of a.data){const e=O.safeParse(o);e.success&&l.set(e.data.Name.toLowerCase(),{id:e.data.Id,name:e.data.Name})}const p=[],A=[];for(const[o,{environment:e,displayName:N}]of f){const m=l.get(o);if(!m){A.push(N);continue}v(e)!=="organisation"&&p.push({id:m.id,name:m.name,tier:v(e),environment:y(e)})}return T({providerAccounts:p,missingAccountNames:A})}function z(s,c){const n=new Map;for(const r of s?.providerAccounts??[])n.set(r.id,r);for(const r of c)n.has(r.id)||n.set(r.id,r);return{...s??{},providerAccounts:Array.from(n.values())}}export{z as mergeReconciledProviderAccounts,b as reconcileProviderAccounts};
@@ -1,6 +1,6 @@
1
1
  import type { ProviderAccount } from "@fjall/util/config";
2
2
  import type { DeployParams } from "../../types/params.js";
3
- import type { OrgConfig } from "../../types/orgConfig.js";
3
+ import type { OrgConfig } from "../../types/config/orgConfig.js";
4
4
  import type { OrganisationOperation } from "../../types/operations.js";
5
5
  import type { DeployServices } from "../serviceFactory.js";
6
6
  /**
@@ -1 +1 @@
1
- import{failure as U}from"@fjall/generator";import{regionSuffix as j}from"../../types/FjallState.js";import{partitionAccounts as W,deployCascadeAccount as v,readPlatformIpamPoolIds as $,deployDomains as X,buildRegionList as Y,buildAccountRegionPairs as z,cascadeHomeRegion as B,CASCADE_MAX_CONCURRENCY as G}from"../cascadeHelpers.js";import{projectScalarSummary as x}from"../cascadeSummary.js";import{getErrorMessage as J,maskSensitiveOutput as C,mapSettledWithConcurrency as K}from"@fjall/util";import{INFRA_STEPS as E}from"./infraSteps.js";async function re(d,D,N,T){const{callbacks:e}=d,{providerAccounts:q,effectiveOrgConfig:R,totalSteps:p,cascadeErrors:l,allCascadeOutputs:b}=T;let S=!1;e.onCascadeStart?.();const H=Date.now();let i=2,P=!1,g,w=!1;const h=[],M=t=>({members:h,...g!==void 0?{platform:g}:{},domainsDeployed:w,errors:l,totalDurationMs:t}),{platformAccount:a,memberAccounts:A}=W(q),O=B(d.orgConfig);if(a){const{id:t,name:o}=E.CASCADE_PLATFORM;e.onStepStart?.(t,o,i,p),e.onCascadePhaseStart?.("platform");let s;const I=Date.now();try{s=await v(d,D,N,a,"platform",e,{orgConfig:R,primaryRegion:O})}catch(c){const r=C(J(c));l.push({accountId:a.id,error:r}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,o,"error",i,p),s=U(new Error(r))}const k=Date.now()-I;if(s.success){P=!0;const c=s.data.skipped===!0;c||(S=!0),s.data.outputs&&b.push({accountId:a.id,outputs:s.data.outputs}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,o,c?"skipped":"completed",i,p),g={accountId:a.id,result:c?"skipped":"succeeded",durationMs:k}}else l.some(c=>c.accountId===a.id)||(l.push({accountId:a.id,error:C(s.error.message)}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,o,"error",i,p)),g={accountId:a.id,result:"failed",durationMs:k,error:C(s.error.message)};i++}let _=new Map;if(P&&a&&(_=await $(D,a,e,d.abortSignal)),d.domainProvider){const t=await X(d.domainProvider,e);w=t.domainsDeployed>0,w&&(S=!0);for(const o of t.errors)l.push({accountId:"domains",error:C(o)})}if(a!==void 0&&!P&&A.length>0){const{id:t,name:o}=E.CASCADE_ACCOUNTS;e.onStepStart?.(t,o,i,p),e.onStepComplete?.(t,o,"skipped",i,p),e.onLog?.("Skipping account cascade \u2014 platform deployment failed; platform is a prerequisite for member accounts.","warn")}else if(A.length>0){const{id:t,name:o}=E.CASCADE_ACCOUNTS;e.onStepStart?.(t,o,i,p),e.onCascadePhaseStart?.("accounts");const s=Y(d.orgConfig),I=z(A,s);(await K(I,G,async({account:r,region:y})=>{const m=_.get(`${r.id}-${j(y)}`),n=Date.now();return{result:await v(d,D,N,r,"account",e,{ipamPoolId:m,orgConfig:R,region:y,primaryRegion:O}),durationMs:Date.now()-n}})).forEach((r,y)=>{const m=I[y];if(!m)return;const n=m.account;if(r.status==="rejected"){const u=C(r.reason instanceof Error?r.reason.message:String(r.reason));h.push({accountId:n.id,accountName:n.name,region:m.region,result:"failed",durationMs:0,error:u}),l.push({accountId:n.id,error:u});return}const{result:f,durationMs:L}=r.value;if(f.success){const u=f.data.skipped===!0;u||(S=!0),f.data.outputs&&b.push({accountId:n.id,outputs:f.data.outputs}),h.push({accountId:n.id,accountName:n.name,region:m.region,result:u?"skipped":"succeeded",durationMs:L})}else{const u=C(f.error.message);h.push({accountId:n.id,accountName:n.name,region:m.region,result:"failed",durationMs:L,error:u}),l.push({accountId:n.id,error:u})}});const c=x(M(0));e.onCascadePhaseComplete?.("accounts"),e.onStepComplete?.(t,o,c.accountsFailed>0?"error":c.accountsSkipped===A.length?"skipped":"completed",i,p)}const F=M(Date.now()-H);return e.onCascadeComplete?.(x(F)),e.onCascadeLedger?.(F),{anyCascadeDeployHappened:S}}export{re as executeCascade};
1
+ import{failure as U}from"@fjall/generator";import{regionSuffix as j}from"../../types/config/FjallState.js";import{partitionAccounts as W,deployCascadeAccount as v,readPlatformIpamPoolIds as $,deployDomains as X,buildRegionList as Y,buildAccountRegionPairs as z,cascadeHomeRegion as B,CASCADE_MAX_CONCURRENCY as G}from"../organisation/cascadeHelpers.js";import{projectScalarSummary as x}from"../organisation/cascadeSummary.js";import{getErrorMessage as J,maskSensitiveOutput as C,mapSettledWithConcurrency as K}from"@fjall/util";import{INFRA_STEPS as E}from"./infraSteps.js";async function re(d,D,N,T){const{callbacks:e}=d,{providerAccounts:q,effectiveOrgConfig:R,totalSteps:p,cascadeErrors:l,allCascadeOutputs:b}=T;let S=!1;e.onCascadeStart?.();const H=Date.now();let i=2,P=!1,g,w=!1;const h=[],M=t=>({members:h,...g!==void 0?{platform:g}:{},domainsDeployed:w,errors:l,totalDurationMs:t}),{platformAccount:a,memberAccounts:A}=W(q),O=B(d.orgConfig);if(a){const{id:t,name:o}=E.CASCADE_PLATFORM;e.onStepStart?.(t,o,i,p),e.onCascadePhaseStart?.("platform");let s;const I=Date.now();try{s=await v(d,D,N,a,"platform",e,{orgConfig:R,primaryRegion:O})}catch(c){const r=C(J(c));l.push({accountId:a.id,error:r}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,o,"error",i,p),s=U(new Error(r))}const k=Date.now()-I;if(s.success){P=!0;const c=s.data.skipped===!0;c||(S=!0),s.data.outputs&&b.push({accountId:a.id,outputs:s.data.outputs}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,o,c?"skipped":"completed",i,p),g={accountId:a.id,result:c?"skipped":"succeeded",durationMs:k}}else l.some(c=>c.accountId===a.id)||(l.push({accountId:a.id,error:C(s.error.message)}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,o,"error",i,p)),g={accountId:a.id,result:"failed",durationMs:k,error:C(s.error.message)};i++}let _=new Map;if(P&&a&&(_=await $(D,a,e,d.abortSignal)),d.domainProvider){const t=await X(d.domainProvider,e);w=t.domainsDeployed>0,w&&(S=!0);for(const o of t.errors)l.push({accountId:"domains",error:C(o)})}if(a!==void 0&&!P&&A.length>0){const{id:t,name:o}=E.CASCADE_ACCOUNTS;e.onStepStart?.(t,o,i,p),e.onStepComplete?.(t,o,"skipped",i,p),e.onLog?.("Skipping account cascade \u2014 platform deployment failed; platform is a prerequisite for member accounts.","warn")}else if(A.length>0){const{id:t,name:o}=E.CASCADE_ACCOUNTS;e.onStepStart?.(t,o,i,p),e.onCascadePhaseStart?.("accounts");const s=Y(d.orgConfig),I=z(A,s);(await K(I,G,async({account:r,region:y})=>{const m=_.get(`${r.id}-${j(y)}`),n=Date.now();return{result:await v(d,D,N,r,"account",e,{ipamPoolId:m,orgConfig:R,region:y,primaryRegion:O}),durationMs:Date.now()-n}})).forEach((r,y)=>{const m=I[y];if(!m)return;const n=m.account;if(r.status==="rejected"){const u=C(r.reason instanceof Error?r.reason.message:String(r.reason));h.push({accountId:n.id,accountName:n.name,region:m.region,result:"failed",durationMs:0,error:u}),l.push({accountId:n.id,error:u});return}const{result:f,durationMs:L}=r.value;if(f.success){const u=f.data.skipped===!0;u||(S=!0),f.data.outputs&&b.push({accountId:n.id,outputs:f.data.outputs}),h.push({accountId:n.id,accountName:n.name,region:m.region,result:u?"skipped":"succeeded",durationMs:L})}else{const u=C(f.error.message);h.push({accountId:n.id,accountName:n.name,region:m.region,result:"failed",durationMs:L,error:u}),l.push({accountId:n.id,error:u})}});const c=x(M(0));e.onCascadePhaseComplete?.("accounts"),e.onStepComplete?.(t,o,c.accountsFailed>0?"error":c.accountsSkipped===A.length?"skipped":"completed",i,p)}const F=M(Date.now()-H);return e.onCascadeComplete?.(x(F)),e.onCascadeLedger?.(F),{anyCascadeDeployHappened:S}}export{re as executeCascade};
@@ -1,4 +1,4 @@
1
- import{join as W}from"node:path";import{success as _,failure as Y}from"@fjall/generator";import{ORGANISATION_TYPES as B,getOrganisationStackName as K}from"../../types/operations.js";import{synthOrFail as q,bootstrapOrFail as z,forwardOutput as J,forwardResourceProgress as Q}from"../contextHelpers.js";import{partitionAccounts as U,probeCascadeRoles as V}from"../cascadeHelpers.js";import{accountTier as X,maskSensitiveOutput as l}from"@fjall/util";import{buildOrgContext as Z,resolveOrgDetails as v}from"./orgContext.js";import{INFRA_STEPS as L,maskAndFail as R,readStackOutputsBestEffort as x}from"./infraSteps.js";import{resolveCascadeAccounts as ee}from"./resolveCascadeAccounts.js";import{executeCascade as te}from"./cascadeExecution.js";import{reconcileOrgTrailOutputs as oe}from"./trailReconciliation.js";async function ge(s,t,O,H){const{callbacks:e,options:$}=s,{providerAccounts:u,effectiveOrgConfig:M,defaultRegion:j}=await ee(s,t),g=await v(t,s.abortSignal);if(!g.success)return R(e,g.error.message);const G=u.find(o=>X(o)==="organisation"),d=Z(s,t,O,"organisation",g.data,void 0,G?.trailLifecycle),m=$?.cascade!==!1,{platformAccount:p,memberAccounts:k}=U(u),P=m&&p!==void 0?1:0,D=m&&k.length>0?1:0,n=2+P+D,I=m?[...p!==void 0?[p]:[],...k]:[];if(I.length>0){const o=await V(t,I,e,s.abortSignal),r=p!==void 0?o.find(a=>a.accountId===p.id):void 0;if(r!==void 0)return R(e,`Pre-flight cascade role check failed for the platform account ${r.accountName} (${r.accountId}): ${r.error}`);for(const a of o)e.onProgress?.({type:"warning",message:l(`Pre-flight cascade role check failed for ${a.accountName} (${a.accountId}) \u2014 its cascade deploy is expected to fail: ${a.error}`)})}e.onCascadeAccountsReconciled?.({hasPlatformAccount:P>0,hasMemberAccounts:D>0});const{id:h,name:S}=L.PREPARE;e.onStepStart?.(h,S,0,n),e.onLog?.("Synthesising organisation infrastructure\u2026","info");const E=await q(t,d,e,"CDK synthesis failed");if(!E.success)return e.onStepComplete?.(h,S,"error",0,n),E;const N=await z(t,d,e);if(!N.success)return e.onStepComplete?.(h,S,"error",0,n),N;e.onStepComplete?.(h,S,"completed",0,n);const{id:C,name:b}=L.ORG_DEPLOY,c=K(B.ORGANISATION);let F=!0;const f=await t.hashService.getTemplateHashes(W(d.path,"cdk.out"));if(f.success){const o=await t.hashService.compareWithState(f.data,d.path);o.success?F=o.data.stackChanges.get(c)??!0:e.onLog?.(l(`Org root change detection failed \u2014 deploying to be safe: ${o.error.message}`),"warn")}else e.onLog?.(l(`Org root template hashing failed \u2014 deploying to be safe: ${f.error.message}`),"warn");const A=F||$?.force===!0||!await t.cfnService.stackExists(c);e.onOrgChangesDetected?.({hasOrgChanges:A});let w;if(A){e.onStepStart?.(C,b,1,n);const o=await t.cdkService.runCdkDeploy(d,c,J(e),Q(e),t.awsProvider);if(!o.success)return e.onStepComplete?.(C,b,"error",1,n),R(e,o.error);w=await x(t,e,c,"Failed to read org stack outputs (non-critical)");const r=f.success?f.data.get(c):void 0;if(r!==void 0){const a=await t.hashService.updateStateAfterDeploy(d.path,new Map([[c,r]]));a.success||e.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy the org root: ${l(a.error.message)}`,"warn")}e.onStepComplete?.(C,b,"completed",1,n)}else e.onLog?.("Organisation root: no infrastructure changes \u2014 skipping deploy","info"),w=await x(t,e,c,"Failed to read org stack outputs (non-critical)");const i=[],y=[];let T=A;if(m&&u.length>0){const{anyCascadeDeployHappened:o}=await te(s,t,O,{providerAccounts:u,effectiveOrgConfig:M,totalSteps:n,cascadeErrors:i,allCascadeOutputs:y});if(o&&(T=!0),await oe(s,t,{orgOutputs:w,allCascadeOutputs:y,orgDetails:g.data,providerAccounts:u,defaultRegion:j}),i.length>0){const r=i.map(a=>` ${a.accountId}: ${a.error}`).join(`
1
+ import{join as W}from"node:path";import{success as _,failure as Y}from"@fjall/generator";import{ORGANISATION_TYPES as B,getOrganisationStackName as K}from"../../types/operations.js";import{synthOrFail as q,bootstrapOrFail as z,forwardOutput as J,forwardResourceProgress as Q}from"../contextHelpers.js";import{partitionAccounts as U,probeCascadeRoles as V}from"../organisation/cascadeHelpers.js";import{accountTier as X,maskSensitiveOutput as l}from"@fjall/util";import{buildOrgContext as Z,resolveOrgDetails as v}from"./orgContext.js";import{INFRA_STEPS as L,maskAndFail as R,readStackOutputsBestEffort as x}from"./infraSteps.js";import{resolveCascadeAccounts as ee}from"./resolveCascadeAccounts.js";import{executeCascade as te}from"./cascadeExecution.js";import{reconcileOrgTrailOutputs as oe}from"./trailReconciliation.js";async function ge(s,t,O,H){const{callbacks:e,options:$}=s,{providerAccounts:u,effectiveOrgConfig:M,defaultRegion:j}=await ee(s,t),g=await v(t,s.abortSignal);if(!g.success)return R(e,g.error.message);const G=u.find(o=>X(o)==="organisation"),d=Z(s,t,O,"organisation",g.data,void 0,G?.trailLifecycle),m=$?.cascade!==!1,{platformAccount:p,memberAccounts:k}=U(u),P=m&&p!==void 0?1:0,D=m&&k.length>0?1:0,n=2+P+D,I=m?[...p!==void 0?[p]:[],...k]:[];if(I.length>0){const o=await V(t,I,e,s.abortSignal),r=p!==void 0?o.find(a=>a.accountId===p.id):void 0;if(r!==void 0)return R(e,`Pre-flight cascade role check failed for the platform account ${r.accountName} (${r.accountId}): ${r.error}`);for(const a of o)e.onProgress?.({type:"warning",message:l(`Pre-flight cascade role check failed for ${a.accountName} (${a.accountId}) \u2014 its cascade deploy is expected to fail: ${a.error}`)})}e.onCascadeAccountsReconciled?.({hasPlatformAccount:P>0,hasMemberAccounts:D>0});const{id:h,name:S}=L.PREPARE;e.onStepStart?.(h,S,0,n),e.onLog?.("Synthesising organisation infrastructure\u2026","info");const E=await q(t,d,e,"CDK synthesis failed");if(!E.success)return e.onStepComplete?.(h,S,"error",0,n),E;const N=await z(t,d,e);if(!N.success)return e.onStepComplete?.(h,S,"error",0,n),N;e.onStepComplete?.(h,S,"completed",0,n);const{id:C,name:b}=L.ORG_DEPLOY,c=K(B.ORGANISATION);let F=!0;const f=await t.hashService.getTemplateHashes(W(d.path,"cdk.out"));if(f.success){const o=await t.hashService.compareWithState(f.data,d.path);o.success?F=o.data.stackChanges.get(c)??!0:e.onLog?.(l(`Org root change detection failed \u2014 deploying to be safe: ${o.error.message}`),"warn")}else e.onLog?.(l(`Org root template hashing failed \u2014 deploying to be safe: ${f.error.message}`),"warn");const A=F||$?.force===!0||!await t.cfnService.stackExists(c);e.onOrgChangesDetected?.({hasOrgChanges:A});let w;if(A){e.onStepStart?.(C,b,1,n);const o=await t.cdkService.runCdkDeploy(d,c,J(e),Q(e),t.awsProvider);if(!o.success)return e.onStepComplete?.(C,b,"error",1,n),R(e,o.error);w=await x(t,e,c,"Failed to read org stack outputs (non-critical)");const r=f.success?f.data.get(c):void 0;if(r!==void 0){const a=await t.hashService.updateStateAfterDeploy(d.path,new Map([[c,r]]));a.success||e.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy the org root: ${l(a.error.message)}`,"warn")}e.onStepComplete?.(C,b,"completed",1,n)}else e.onLog?.("Organisation root: no infrastructure changes \u2014 skipping deploy","info"),w=await x(t,e,c,"Failed to read org stack outputs (non-critical)");const i=[],y=[];let T=A;if(m&&u.length>0){const{anyCascadeDeployHappened:o}=await te(s,t,O,{providerAccounts:u,effectiveOrgConfig:M,totalSteps:n,cascadeErrors:i,allCascadeOutputs:y});if(o&&(T=!0),await oe(s,t,{orgOutputs:w,allCascadeOutputs:y,orgDetails:g.data,providerAccounts:u,defaultRegion:j}),i.length>0){const r=i.map(a=>` ${a.accountId}: ${a.error}`).join(`
2
2
  `);e.onLog?.(l(`Cascade failed for ${i.length} target(s):
3
3
  ${r}`),"warn")}}if(i.length>0){const o=i.map(a=>l(`${a.accountId}: ${a.error}`)).join(`
4
4
  `),r=new Error(`Organisation root deployed, but the cascade failed for ${i.length} target(s):
@@ -1,6 +1,6 @@
1
1
  import type { ProviderAccount } from "@fjall/util/config";
2
2
  import type { DeployParams } from "../../types/params.js";
3
- import type { OrgConfig } from "../../types/orgConfig.js";
3
+ import type { OrgConfig } from "../../types/config/orgConfig.js";
4
4
  import type { DeployServices } from "../serviceFactory.js";
5
5
  /**
6
6
  * Resolve the deployable provider-account set for an organisation cascade:
@@ -1 +1 @@
1
- import{cascadeHomeRegion as u}from"../cascadeHelpers.js";import{reconcileProviderAccounts as l,mergeReconciledProviderAccounts as d}from"../reconcileProviderAccounts.js";import{accountTier as f,maskSensitiveOutput as s}from"@fjall/util";async function v(e,g){const{callbacks:c}=e;let o=e.orgConfig?.providerAccounts??[];if(o.length===0||o.every(n=>f(n)==="organisation")){const n=await l(g,e.workingDirectory);if(n.success){const{providerAccounts:i,missingAccountNames:t}=n.data;i.length>0&&(o=d(e.orgConfig,i).providerAccounts,c.onLog?.(`Reconciled ${i.length} account(s) from AWS Organizations`,"info")),t.length>0&&(c.onCascadeMissingAccounts?.(t),c.onProgress?.({type:"warning",message:s(`Accounts declared in ACCOUNTS but not yet in AWS Organizations (cascade will skip): ${t.join(", ")}`)}))}else c.onProgress?.({type:"warning",message:s(`Could not reconcile accounts from AWS Organizations \u2014 cascade may skip accounts: ${n.error.message}`)})}const r=u(e.orgConfig);o=o.map(n=>n.region!==void 0?n:{...n,region:r});const a=e.orgConfig!==void 0?{...e.orgConfig,providerAccounts:o}:o.length>0?{providerAccounts:o}:void 0;return{providerAccounts:o,effectiveOrgConfig:a,defaultRegion:r}}export{v as resolveCascadeAccounts};
1
+ import{cascadeHomeRegion as u}from"../organisation/cascadeHelpers.js";import{reconcileProviderAccounts as l,mergeReconciledProviderAccounts as d}from"../organisation/reconcileProviderAccounts.js";import{accountTier as f,maskSensitiveOutput as s}from"@fjall/util";async function v(e,g){const{callbacks:c}=e;let o=e.orgConfig?.providerAccounts??[];if(o.length===0||o.every(n=>f(n)==="organisation")){const n=await l(g,e.workingDirectory);if(n.success){const{providerAccounts:i,missingAccountNames:t}=n.data;i.length>0&&(o=d(e.orgConfig,i).providerAccounts,c.onLog?.(`Reconciled ${i.length} account(s) from AWS Organizations`,"info")),t.length>0&&(c.onCascadeMissingAccounts?.(t),c.onProgress?.({type:"warning",message:s(`Accounts declared in ACCOUNTS but not yet in AWS Organizations (cascade will skip): ${t.join(", ")}`)}))}else c.onProgress?.({type:"warning",message:s(`Could not reconcile accounts from AWS Organizations \u2014 cascade may skip accounts: ${n.error.message}`)})}const r=u(e.orgConfig);o=o.map(n=>n.region!==void 0?n:{...n,region:r});const a=e.orgConfig!==void 0?{...e.orgConfig,providerAccounts:o}:o.length>0?{providerAccounts:o}:void 0;return{providerAccounts:o,effectiveOrgConfig:a,defaultRegion:r}}export{v as resolveCascadeAccounts};
@@ -6,7 +6,7 @@ import type { ResourceEvent } from "../../types/events.js";
6
6
  import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
7
7
  import { type ApplicationStack } from "../../types/operations.js";
8
8
  import type { ParallelDeploymentResult } from "../../types/deployment/parallel.js";
9
- import type { AppResourceFlags } from "../../types/patternDetection.js";
9
+ import type { AppResourceFlags } from "../../types/detection/patternDetection.js";
10
10
  import { type Result } from "@fjall/generator";
11
11
  import { ApplicationError, type StackDeploymentData } from "../../types/application/ApplicationServiceTypes.js";
12
12
  import { type DestroyAllStacksCallbacks } from "./applicationStackHelpers.js";
@@ -2,7 +2,7 @@ import type { ResourceEvent } from "../../types/events.js";
2
2
  import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
3
3
  import { type ApplicationStack } from "../../types/operations.js";
4
4
  import type { ParallelDeploymentResult } from "../../types/deployment/parallel.js";
5
- import type { AppResourceFlags } from "../../types/patternDetection.js";
5
+ import type { AppResourceFlags } from "../../types/detection/patternDetection.js";
6
6
  import { type Result } from "@fjall/generator";
7
7
  import { ApplicationError, type StackDeploymentData } from "../../types/application/ApplicationServiceTypes.js";
8
8
  import type { ApplicationStackService } from "./ApplicationStackService.js";
@@ -1,7 +1,7 @@
1
1
  import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
2
2
  import type { CallerIdentity } from "../../types/deployment/DeploymentServiceTypes.js";
3
3
  import type { StackOutputsRecord } from "../../types/deployment/cloudformation.js";
4
- import type { OrgConfig } from "../../types/orgConfig.js";
4
+ import type { OrgConfig } from "../../types/config/orgConfig.js";
5
5
  /**
6
6
  * Builder for creating standardised CDK deployment contexts.
7
7
  * Ensures all required fields are present with proper defaults.
@@ -1 +1 @@
1
- import{createHash as C}from"crypto";import{readFile as T,readdir as _}from"fs/promises";import{join as m,basename as E}from"path";import{fileExists as F}from"@fjall/util/fsHelpers";import{success as h,failure as i}from"@fjall/generator";import{BaseServiceError as k}from"../../types/errors/ServiceError.js";import{readStateFile as p,writeStateFile as A,readStateFileAt as w,writeStateFileAt as $,createEmptyState as g,updateTemplateHash as y,getStateFilePath as S}from"../../types/FjallState.js";class l extends k{errorType;constructor(e,t,r,a=!1){super(`TEMPLATE_HASH_${t.toUpperCase()}`,e,r,a),this.errorType=t}}const u=".template.json";class J{async computeTemplateHash(e){try{const t=await T(e,"utf-8"),r=JSON.stringify(JSON.parse(t)),a=C("sha256").update(r).digest("hex");return h(a)}catch(t){return i(new l(`Failed to hash template: ${e}`,"hash_failed",{path:e,error:t}))}}async getTemplateHashes(e){if(!await F(e))return i(new l(`CDK output directory not found: ${e}`,"cdk_out_not_found",{path:e}));try{const r=(await _(e)).filter(s=>s.endsWith(u)),a=new Map;for(const s of r){const n=E(s,u),o=m(e,s),c=await this.computeTemplateHash(o);if(!c.success)return i(c.error);a.set(n,c.data)}return h(a)}catch(t){return i(new l(`Failed to read CDK output directory: ${e}`,"read_failed",{path:e,error:t}))}}async compareWithState(e,t){const r=await p(t),a=new Map;let s=0,n=0;for(const[o,c]of e){const f=r?.templateHashes[o],d=!f||f.hash!==c;a.set(o,d),d?s++:n++}if(r?.templateHashes)for(const o of Object.keys(r.templateHashes))e.has(o)||(a.set(o,!0),s++);return h({stackChanges:a,currentHashes:e,changedCount:s,unchangedCount:n})}async updateStateAfterDeploy(e,t,r){try{let a=await p(e)??g();for(const[s,n]of t){const o=r?.get(s);a=y(a,s,n,o)}return await A(e,a),h(void 0)}catch(a){return i(new l(`Failed to write state file: ${S(e)}`,"state_write_failed",{appPath:e,error:a}))}}async compareCascadeStack(e,t,r){const a=m(e,`${t}${u}`),s=await this.computeTemplateHash(a);if(!s.success)return{changed:!0,currentHash:void 0};const n=s.data,c=(await w(r))?.templateHashes[t];return{changed:!c||c.hash!==n,currentHash:n}}async persistCascadeStack(e,t,r,a){try{const s=await w(e)??g(),n=y(s,t,r,a);return await $(e,n),h(void 0)}catch(s){return i(new l(`Failed to write cascade state file: ${e}`,"state_write_failed",{stateFilePath:e,error:s}))}}getStateFilePath(e){return S(e)}stackHasChanges(e,t){return e.stackChanges.get(t)??!0}getChangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>t).map(([t])=>t)}getUnchangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>!t).map(([t])=>t)}}export{l as TemplateHashError,J as TemplateHashService};
1
+ import{createHash as C}from"crypto";import{readFile as T,readdir as _}from"fs/promises";import{join as m,basename as E}from"path";import{fileExists as F}from"@fjall/util/fsHelpers";import{success as h,failure as i}from"@fjall/generator";import{BaseServiceError as k}from"../../types/errors/ServiceError.js";import{readStateFile as p,writeStateFile as A,readStateFileAt as w,writeStateFileAt as $,createEmptyState as g,updateTemplateHash as y,getStateFilePath as S}from"../../types/config/FjallState.js";class l extends k{errorType;constructor(e,t,r,a=!1){super(`TEMPLATE_HASH_${t.toUpperCase()}`,e,r,a),this.errorType=t}}const u=".template.json";class J{async computeTemplateHash(e){try{const t=await T(e,"utf-8"),r=JSON.stringify(JSON.parse(t)),a=C("sha256").update(r).digest("hex");return h(a)}catch(t){return i(new l(`Failed to hash template: ${e}`,"hash_failed",{path:e,error:t}))}}async getTemplateHashes(e){if(!await F(e))return i(new l(`CDK output directory not found: ${e}`,"cdk_out_not_found",{path:e}));try{const r=(await _(e)).filter(s=>s.endsWith(u)),a=new Map;for(const s of r){const n=E(s,u),o=m(e,s),c=await this.computeTemplateHash(o);if(!c.success)return i(c.error);a.set(n,c.data)}return h(a)}catch(t){return i(new l(`Failed to read CDK output directory: ${e}`,"read_failed",{path:e,error:t}))}}async compareWithState(e,t){const r=await p(t),a=new Map;let s=0,n=0;for(const[o,c]of e){const f=r?.templateHashes[o],d=!f||f.hash!==c;a.set(o,d),d?s++:n++}if(r?.templateHashes)for(const o of Object.keys(r.templateHashes))e.has(o)||(a.set(o,!0),s++);return h({stackChanges:a,currentHashes:e,changedCount:s,unchangedCount:n})}async updateStateAfterDeploy(e,t,r){try{let a=await p(e)??g();for(const[s,n]of t){const o=r?.get(s);a=y(a,s,n,o)}return await A(e,a),h(void 0)}catch(a){return i(new l(`Failed to write state file: ${S(e)}`,"state_write_failed",{appPath:e,error:a}))}}async compareCascadeStack(e,t,r){const a=m(e,`${t}${u}`),s=await this.computeTemplateHash(a);if(!s.success)return{changed:!0,currentHash:void 0};const n=s.data,c=(await w(r))?.templateHashes[t];return{changed:!c||c.hash!==n,currentHash:n}}async persistCascadeStack(e,t,r,a){try{const s=await w(e)??g(),n=y(s,t,r,a);return await $(e,n),h(void 0)}catch(s){return i(new l(`Failed to write cascade state file: ${e}`,"state_write_failed",{stateFilePath:e,error:s}))}}getStateFilePath(e){return S(e)}stackHasChanges(e,t){return e.stackChanges.get(t)??!0}getChangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>t).map(([t])=>t)}getUnchangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>!t).map(([t])=>t)}}export{l as TemplateHashError,J as TemplateHashService};
@@ -1,7 +1,7 @@
1
1
  import type { ProgressEvent, ResourceEvent, AwsAuthResult, CascadeDeploymentResult, CascadePhase, BuildPushStartEvent, BuildPushProgressEvent, BuildPushCompleteEvent, TaskDefRegisteredEvent, ECSCompleteEvent, MigrationsStartEvent, MigrationsCompleteEvent, TrailMigrationPhaseEvent } from "./events.js";
2
2
  import type { TrailLifecycleState } from "@fjall/util/config";
3
- import type { DetectionResult } from "./detection.js";
4
- import type { CascadeLedger } from "../orchestration/cascadeSummary.js";
3
+ import type { DetectionResult } from "./detection/detection.js";
4
+ import type { CascadeLedger } from "../orchestration/organisation/cascadeSummary.js";
5
5
  export type StepCompleteStatus = "completed" | "skipped" | "error";
6
6
  /**
7
7
  * Structured payload for the "quarantine-suspected" stack-cleanup phase:
@@ -0,0 +1 @@
1
+ import{existsSync as s,readFileSync as a}from"fs";import{join as i}from"path";import{logger as f}from"@fjall/util/logger";import{getErrorMessage as m}from"@fjall/util";import{APPLICATION_STACKS as e}from"../operations.js";import{INFRASTRUCTURE_FILENAME as u}from"../constants.js";import{OPENNEXT_PATTERNS as g,isOpenNextPattern as C}from"./patternTypes.js";function p(t){const r=o=>t.some(n=>n.endsWith(o));return{hasNetwork:r(e.NETWORK),hasCompute:r(e.COMPUTE),hasDatabase:r(e.DATABASE),hasStorage:r(e.STORAGE),hasMessaging:r(e.MESSAGING),hasCdn:r(e.CDN)}}function h(t){try{const r=i(t,u);return s(r)?a(r,"utf-8"):null}catch(r){return f.debug("patternDetection","Failed to read infrastructure file",{appPath:t,error:m(r)}),null}}export{g as OPENNEXT_PATTERNS,p as deriveResourcesFromManifestStacks,C as isOpenNextPattern,h as readInfrastructureContent};
@@ -8,7 +8,7 @@
8
8
  import type { Result } from "@fjall/generator";
9
9
  import type { PatternType } from "@fjall/generator";
10
10
  import type { ApplicationStack } from "./operations.js";
11
- import type { AppResourceFlags } from "./patternTypes.js";
11
+ import type { AppResourceFlags } from "./detection/patternTypes.js";
12
12
  /** Result of a builder's detect() phase */
13
13
  export interface FrameworkDetection {
14
14
  /** Whether this builder can handle the app */
@@ -7,11 +7,11 @@ export type { ProgressEvent, ProgressEventType, ResourceEvent, AwsAuthResult, Ca
7
7
  export { TRAIL_MIGRATION_PHASES, TRAIL_MIGRATION_STATUSES } from "./events.js";
8
8
  export type { ApiClientInterface, EntitlementsData } from "./apiClient.js";
9
9
  export type { DeployParams, DeployOptions, DeploymentType, DeployResult, DestroyParams, DestroyOptions, DestroyResult } from "./params.js";
10
- export type { OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession } from "./orgConfig.js";
11
- export type { Entitlements } from "./entitlements.js";
10
+ export type { OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession } from "./config/orgConfig.js";
11
+ export type { Entitlements } from "./config/entitlements.js";
12
12
  export type { StackOutput, StackOutputsRecord, DeploymentContext, StepOutput, ApplicationDeploymentContext, CallerIdentity } from "./deployment/index.js";
13
13
  export { stubCallerIdentity } from "./deployment/index.js";
14
- export type { DetectionResult } from "./detection.js";
14
+ export type { DetectionResult } from "./detection/detection.js";
15
15
  export type { ValidationResult } from "./validation.js";
16
16
  export type { ProgressCallbacks } from "./ProgressEvent.js";
17
17
  export { ProgressReporter } from "./ProgressEvent.js";
@@ -19,13 +19,13 @@ export type { ApplicationStack, OrganisationType, DeploymentOperation, Applicati
19
19
  export { APPLICATION_STACKS, ORGANISATION_TYPES, APPLICATION_DEPLOY_ORDER, APPLICATION_DESTROY_ORDER, OPENNEXT_DEPLOY_ORDER, OPENNEXT_DESTROY_ORDER, PARALLEL_DEPLOY_GROUPS, PARALLEL_DESTROY_GROUPS, OPENNEXT_PARALLEL_GROUPS, isApplicationOperation, isOrganisationOperation, getParallelDeployGroups, getParallelDestroyGroups, getApplicationDeployOrder, getApplicationDestroyOrder, getApplicationStackName, getOrganisationStackName, isApplicationStack, getApplicationStepName, getApplicationStepId, toPascalCase } from "./operations.js";
20
20
  export type { ParallelDeploymentResult, ParallelDeployGroup, ParallelOperation, ParallelOperationType, ParallelOperationState } from "./deployment/index.js";
21
21
  export { PARALLEL_OPERATION_TYPES } from "./deployment/index.js";
22
- export type { OpenNextPattern, AppResourceFlags } from "./patternTypes.js";
23
- export { isOpenNextPattern, OPENNEXT_PATTERNS } from "./patternTypes.js";
24
- export { deriveResourcesFromManifestStacks } from "./patternDetection.js";
22
+ export type { OpenNextPattern, AppResourceFlags } from "./detection/patternTypes.js";
23
+ export { isOpenNextPattern, OPENNEXT_PATTERNS } from "./detection/patternTypes.js";
24
+ export { deriveResourcesFromManifestStacks } from "./detection/patternDetection.js";
25
25
  export { STACK_NOT_FOUND_PATTERN, STACK_FAILED_STATE_PATTERN, CDK_NO_STACKS_MATCH, INFRASTRUCTURE_FILENAME } from "./constants.js";
26
26
  export { ApplicationError, wrapApplicationError, type ApplicationErrorType, type StackDeploymentData, type StackDeploymentOptions, type ApplicationDeploymentData, type ApplicationDestructionData } from "./application/index.js";
27
- export type { FjallStateFile, TemplateHashEntry } from "./FjallState.js";
28
- export { FjallStateFileSchema, readStateFile, writeStateFile, createEmptyState, deleteStateFile, updateTemplateHash, getStateFilePath, regionSuffix } from "./FjallState.js";
27
+ export type { FjallStateFile, TemplateHashEntry } from "./config/FjallState.js";
28
+ export { FjallStateFileSchema, readStateFile, writeStateFile, createEmptyState, deleteStateFile, updateTemplateHash, getStateFilePath, regionSuffix } from "./config/FjallState.js";
29
29
  export type { FrameworkBuilder, FrameworkDetection, BuildPlan, BuildCommand, BuildCallbacks, DetectionContext, BuildOptions } from "./frameworkBuilder.js";
30
30
  export { STEP_IDS, STEP_NAMES, INFRASTRUCTURE_STEP_NAMES, INFRA_STEP_NAME } from "./stepDefinitions.js";
31
31
  export type { StepId, InfrastructureStepName, StepDefinition, StepDeploymentType, Operation, StepContext } from "./stepDefinitions.js";
@@ -1 +1 @@
1
- import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as A,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as T,DEPLOYMENT_EVENT_STATUS_REASON_MAX as r,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as S,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as _,CASCADE_PHASES as a,CASCADE_ACCOUNT_STATUSES as o,toCascadePhase as p}from"./deploymentEventSchema.js";import{isQuarantineDetail as i,isRetainedBucketsDetail as N}from"./callbacks.js";import{TRAIL_MIGRATION_PHASES as R,TRAIL_MIGRATION_STATUSES as l}from"./events.js";import{stubCallerIdentity as L}from"./deployment/index.js";import{ProgressReporter as n}from"./ProgressEvent.js";import{APPLICATION_STACKS as m,ORGANISATION_TYPES as C,APPLICATION_DEPLOY_ORDER as c,APPLICATION_DESTROY_ORDER as M,OPENNEXT_DEPLOY_ORDER as f,OPENNEXT_DESTROY_ORDER as x,PARALLEL_DEPLOY_GROUPS as Y,PARALLEL_DESTROY_GROUPS as g,OPENNEXT_PARALLEL_GROUPS as F,isApplicationOperation as U,isOrganisationOperation as d,getParallelDeployGroups as G,getParallelDestroyGroups as u,getApplicationDeployOrder as y,getApplicationDestroyOrder as X,getApplicationStackName as h,getOrganisationStackName as k,isApplicationStack as K,getApplicationStepName as V,getApplicationStepId as H,toPascalCase as v}from"./operations.js";import{PARALLEL_OPERATION_TYPES as b}from"./deployment/index.js";import{isOpenNextPattern as B,OPENNEXT_PATTERNS as Q}from"./patternTypes.js";import{deriveResourcesFromManifestStacks as z}from"./patternDetection.js";import{STACK_NOT_FOUND_PATTERN as W,STACK_FAILED_STATE_PATTERN as Z,CDK_NO_STACKS_MATCH as $,INFRASTRUCTURE_FILENAME as ee}from"./constants.js";import{ApplicationError as te,wrapApplicationError as Ae}from"./application/index.js";import{FjallStateFileSchema as re,readStateFile as Se,writeStateFile as _e,createEmptyState as ae,deleteStateFile as oe,updateTemplateHash as pe,getStateFilePath as Oe,regionSuffix as ie}from"./FjallState.js";import{STEP_IDS as Pe,STEP_NAMES as Re,INFRASTRUCTURE_STEP_NAMES as le,INFRA_STEP_NAME as De}from"./stepDefinitions.js";export{c as APPLICATION_DEPLOY_ORDER,M as APPLICATION_DESTROY_ORDER,m as APPLICATION_STACKS,te as ApplicationError,o as CASCADE_ACCOUNT_STATUSES,a as CASCADE_PHASES,$ as CDK_NO_STACKS_MATCH,S as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,T as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,r as DEPLOYMENT_EVENT_STATUS_REASON_MAX,_ as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,A as DEPLOYMENT_EVENT_TYPES,t as DeploymentEventSchema,re as FjallStateFileSchema,ee as INFRASTRUCTURE_FILENAME,le as INFRASTRUCTURE_STEP_NAMES,De as INFRA_STEP_NAME,f as OPENNEXT_DEPLOY_ORDER,x as OPENNEXT_DESTROY_ORDER,F as OPENNEXT_PARALLEL_GROUPS,Q as OPENNEXT_PATTERNS,C as ORGANISATION_TYPES,Y as PARALLEL_DEPLOY_GROUPS,g as PARALLEL_DESTROY_GROUPS,b as PARALLEL_OPERATION_TYPES,n as ProgressReporter,Z as STACK_FAILED_STATE_PATTERN,W as STACK_NOT_FOUND_PATTERN,Pe as STEP_IDS,Re as STEP_NAMES,R as TRAIL_MIGRATION_PHASES,l as TRAIL_MIGRATION_STATUSES,ae as createEmptyState,oe as deleteStateFile,z as deriveResourcesFromManifestStacks,y as getApplicationDeployOrder,X as getApplicationDestroyOrder,h as getApplicationStackName,H as getApplicationStepId,V as getApplicationStepName,k as getOrganisationStackName,G as getParallelDeployGroups,u as getParallelDestroyGroups,Oe as getStateFilePath,U as isApplicationOperation,K as isApplicationStack,B as isOpenNextPattern,d as isOrganisationOperation,i as isQuarantineDetail,N as isRetainedBucketsDetail,Se as readStateFile,ie as regionSuffix,L as stubCallerIdentity,p as toCascadePhase,v as toPascalCase,pe as updateTemplateHash,Ae as wrapApplicationError,_e as writeStateFile};
1
+ import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as A,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as T,DEPLOYMENT_EVENT_STATUS_REASON_MAX as r,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as S,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as _,CASCADE_PHASES as a,CASCADE_ACCOUNT_STATUSES as o,toCascadePhase as p}from"./deploymentEventSchema.js";import{isQuarantineDetail as i,isRetainedBucketsDetail as N}from"./callbacks.js";import{TRAIL_MIGRATION_PHASES as R,TRAIL_MIGRATION_STATUSES as l}from"./events.js";import{stubCallerIdentity as L}from"./deployment/index.js";import{ProgressReporter as n}from"./ProgressEvent.js";import{APPLICATION_STACKS as m,ORGANISATION_TYPES as C,APPLICATION_DEPLOY_ORDER as c,APPLICATION_DESTROY_ORDER as M,OPENNEXT_DEPLOY_ORDER as f,OPENNEXT_DESTROY_ORDER as x,PARALLEL_DEPLOY_GROUPS as Y,PARALLEL_DESTROY_GROUPS as g,OPENNEXT_PARALLEL_GROUPS as F,isApplicationOperation as U,isOrganisationOperation as d,getParallelDeployGroups as G,getParallelDestroyGroups as u,getApplicationDeployOrder as y,getApplicationDestroyOrder as X,getApplicationStackName as h,getOrganisationStackName as k,isApplicationStack as K,getApplicationStepName as V,getApplicationStepId as H,toPascalCase as v}from"./operations.js";import{PARALLEL_OPERATION_TYPES as b}from"./deployment/index.js";import{isOpenNextPattern as B,OPENNEXT_PATTERNS as Q}from"./detection/patternTypes.js";import{deriveResourcesFromManifestStacks as z}from"./detection/patternDetection.js";import{STACK_NOT_FOUND_PATTERN as W,STACK_FAILED_STATE_PATTERN as Z,CDK_NO_STACKS_MATCH as $,INFRASTRUCTURE_FILENAME as ee}from"./constants.js";import{ApplicationError as te,wrapApplicationError as Ae}from"./application/index.js";import{FjallStateFileSchema as re,readStateFile as Se,writeStateFile as _e,createEmptyState as ae,deleteStateFile as oe,updateTemplateHash as pe,getStateFilePath as Oe,regionSuffix as ie}from"./config/FjallState.js";import{STEP_IDS as Pe,STEP_NAMES as Re,INFRASTRUCTURE_STEP_NAMES as le,INFRA_STEP_NAME as De}from"./stepDefinitions.js";export{c as APPLICATION_DEPLOY_ORDER,M as APPLICATION_DESTROY_ORDER,m as APPLICATION_STACKS,te as ApplicationError,o as CASCADE_ACCOUNT_STATUSES,a as CASCADE_PHASES,$ as CDK_NO_STACKS_MATCH,S as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,T as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,r as DEPLOYMENT_EVENT_STATUS_REASON_MAX,_ as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,A as DEPLOYMENT_EVENT_TYPES,t as DeploymentEventSchema,re as FjallStateFileSchema,ee as INFRASTRUCTURE_FILENAME,le as INFRASTRUCTURE_STEP_NAMES,De as INFRA_STEP_NAME,f as OPENNEXT_DEPLOY_ORDER,x as OPENNEXT_DESTROY_ORDER,F as OPENNEXT_PARALLEL_GROUPS,Q as OPENNEXT_PATTERNS,C as ORGANISATION_TYPES,Y as PARALLEL_DEPLOY_GROUPS,g as PARALLEL_DESTROY_GROUPS,b as PARALLEL_OPERATION_TYPES,n as ProgressReporter,Z as STACK_FAILED_STATE_PATTERN,W as STACK_NOT_FOUND_PATTERN,Pe as STEP_IDS,Re as STEP_NAMES,R as TRAIL_MIGRATION_PHASES,l as TRAIL_MIGRATION_STATUSES,ae as createEmptyState,oe as deleteStateFile,z as deriveResourcesFromManifestStacks,y as getApplicationDeployOrder,X as getApplicationDestroyOrder,h as getApplicationStackName,H as getApplicationStepId,V as getApplicationStepName,k as getOrganisationStackName,G as getParallelDeployGroups,u as getParallelDestroyGroups,Oe as getStateFilePath,U as isApplicationOperation,K as isApplicationStack,B as isOpenNextPattern,d as isOrganisationOperation,i as isQuarantineDetail,N as isRetainedBucketsDetail,Se as readStateFile,ie as regionSuffix,L as stubCallerIdentity,p as toCascadePhase,v as toPascalCase,pe as updateTemplateHash,Ae as wrapApplicationError,_e as writeStateFile};
@@ -1,4 +1,4 @@
1
- import { type AppResourceFlags } from "./patternTypes.js";
1
+ import { type AppResourceFlags } from "./detection/patternTypes.js";
2
2
  import { type StepId } from "./stepDefinitions.js";
3
3
  import { toPascalCase } from "@fjall/util";
4
4
  /**
@@ -1 +1 @@
1
- import{isOpenNextPattern as i}from"./patternTypes.js";import{STEP_IDS as s}from"./stepDefinitions.js";import{toPascalCase as E}from"@fjall/util";const t={NETWORK:"Network",DATABASE:"Database",STORAGE:"Storage",COMPUTE:"Compute",CDN:"Cdn",MESSAGING:"Messaging"},C={ORGANISATION:"organisation",PLATFORM:"platform",ACCOUNT:"account"};function G(e){return e.kind==="application"}function g(e){return e.kind==="organisation"}const T=[t.NETWORK,t.DATABASE,t.COMPUTE],u=[t.COMPUTE,t.DATABASE,t.NETWORK],P=[t.NETWORK,t.STORAGE,t.MESSAGING,t.DATABASE,t.COMPUTE,t.CDN],N=[t.CDN,t.COMPUTE,t.DATABASE,t.MESSAGING,t.STORAGE,t.NETWORK],n={PHASE_1:{phase:1,stacks:[t.NETWORK],description:"Network infrastructure"},PHASE_2:{phase:2,stacks:[t.STORAGE,t.MESSAGING,t.DATABASE],description:"Storage and database resources (parallel)"},PHASE_3:{phase:3,stacks:[t.COMPUTE],description:"Compute resources"},PHASE_4:{phase:4,stacks:[t.CDN],description:"CDN distribution"}},f={NEXTJS_PARALLEL:{stacks:[t.STORAGE,t.MESSAGING,t.DATABASE],includeBuild:!0,description:"Build + Storage/Messaging/Database (parallel)"},PAYLOAD_PARALLEL:{stacks:[t.STORAGE,t.MESSAGING],includeBuild:!0,description:"Build + Storage/Messaging (parallel)"}};function I(){return[n.PHASE_1,n.PHASE_2,n.PHASE_3,n.PHASE_4]}const a={PHASE_1:{phase:1,stacks:[t.CDN],description:"CDN distribution"},PHASE_2:{phase:2,stacks:[t.COMPUTE],description:"Compute resources"},PHASE_3:{phase:3,stacks:[t.DATABASE,t.MESSAGING,t.STORAGE],description:"Database, messaging, and storage (parallel)"},PHASE_4:{phase:4,stacks:[t.NETWORK],description:"Network infrastructure"}};function L(){return[a.PHASE_1,a.PHASE_2,a.PHASE_3,a.PHASE_4]}const S={Network:1,Storage:2,Messaging:2,Database:2,Compute:3,Cdn:4};function M(e){const{pattern:o,resources:r}=e??{};return i(o)?P:r?p(r,"asc"):T}function h(e){const{pattern:o,resources:r}=e??{};return i(o)?N:r?p(r,"desc"):u}function p(e,o){const r=[];return e.hasNetwork&&r.push(t.NETWORK),e.hasStorage&&r.push(t.STORAGE),e.hasMessaging&&r.push(t.MESSAGING),e.hasDatabase&&r.push(t.DATABASE),e.hasCompute&&r.push(t.COMPUTE),e.hasCdn&&r.push(t.CDN),r.sort((c,O)=>{const A=S[c]-S[O];return o==="asc"?A:-A})}function x(e,o){return`${E(e)}${o}`}function k(e){switch(e){case"organisation":return"Organisation";case"platform":return"Platform";case"account":return"Account";default:{const o=e;throw new Error(`Unsupported organisation type: ${String(o)}`)}}}const D=new Set(Object.values(t));function m(e){return D.has(e)}function y(e,o){return`${o==="deploy"?"Deploying":"Destroying"} ${e.toLowerCase()} infrastructure`}const R={[t.NETWORK]:{deploy:s.NETWORK,destroy:s.NETWORK_DESTROY},[t.DATABASE]:{deploy:s.DATABASE,destroy:s.DATABASE_DESTROY},[t.STORAGE]:{deploy:s.STORAGE,destroy:s.STORAGE_DESTROY},[t.COMPUTE]:{deploy:s.COMPUTE,destroy:s.COMPUTE_DESTROY},[t.CDN]:{deploy:s.CDN,destroy:s.CDN_DESTROY},[t.MESSAGING]:{deploy:s.MESSAGING,destroy:s.MESSAGING_DESTROY}};function U(e,o){return R[e][o]}export{T as APPLICATION_DEPLOY_ORDER,u as APPLICATION_DESTROY_ORDER,t as APPLICATION_STACKS,P as OPENNEXT_DEPLOY_ORDER,N as OPENNEXT_DESTROY_ORDER,f as OPENNEXT_PARALLEL_GROUPS,C as ORGANISATION_TYPES,n as PARALLEL_DEPLOY_GROUPS,a as PARALLEL_DESTROY_GROUPS,M as getApplicationDeployOrder,h as getApplicationDestroyOrder,x as getApplicationStackName,U as getApplicationStepId,y as getApplicationStepName,k as getOrganisationStackName,I as getParallelDeployGroups,L as getParallelDestroyGroups,G as isApplicationOperation,m as isApplicationStack,g as isOrganisationOperation,E as toPascalCase};
1
+ import{isOpenNextPattern as i}from"./detection/patternTypes.js";import{STEP_IDS as s}from"./stepDefinitions.js";import{toPascalCase as E}from"@fjall/util";const t={NETWORK:"Network",DATABASE:"Database",STORAGE:"Storage",COMPUTE:"Compute",CDN:"Cdn",MESSAGING:"Messaging"},C={ORGANISATION:"organisation",PLATFORM:"platform",ACCOUNT:"account"};function G(e){return e.kind==="application"}function g(e){return e.kind==="organisation"}const T=[t.NETWORK,t.DATABASE,t.COMPUTE],u=[t.COMPUTE,t.DATABASE,t.NETWORK],P=[t.NETWORK,t.STORAGE,t.MESSAGING,t.DATABASE,t.COMPUTE,t.CDN],N=[t.CDN,t.COMPUTE,t.DATABASE,t.MESSAGING,t.STORAGE,t.NETWORK],n={PHASE_1:{phase:1,stacks:[t.NETWORK],description:"Network infrastructure"},PHASE_2:{phase:2,stacks:[t.STORAGE,t.MESSAGING,t.DATABASE],description:"Storage and database resources (parallel)"},PHASE_3:{phase:3,stacks:[t.COMPUTE],description:"Compute resources"},PHASE_4:{phase:4,stacks:[t.CDN],description:"CDN distribution"}},f={NEXTJS_PARALLEL:{stacks:[t.STORAGE,t.MESSAGING,t.DATABASE],includeBuild:!0,description:"Build + Storage/Messaging/Database (parallel)"},PAYLOAD_PARALLEL:{stacks:[t.STORAGE,t.MESSAGING],includeBuild:!0,description:"Build + Storage/Messaging (parallel)"}};function I(){return[n.PHASE_1,n.PHASE_2,n.PHASE_3,n.PHASE_4]}const a={PHASE_1:{phase:1,stacks:[t.CDN],description:"CDN distribution"},PHASE_2:{phase:2,stacks:[t.COMPUTE],description:"Compute resources"},PHASE_3:{phase:3,stacks:[t.DATABASE,t.MESSAGING,t.STORAGE],description:"Database, messaging, and storage (parallel)"},PHASE_4:{phase:4,stacks:[t.NETWORK],description:"Network infrastructure"}};function L(){return[a.PHASE_1,a.PHASE_2,a.PHASE_3,a.PHASE_4]}const S={Network:1,Storage:2,Messaging:2,Database:2,Compute:3,Cdn:4};function M(e){const{pattern:o,resources:r}=e??{};return i(o)?P:r?p(r,"asc"):T}function h(e){const{pattern:o,resources:r}=e??{};return i(o)?N:r?p(r,"desc"):u}function p(e,o){const r=[];return e.hasNetwork&&r.push(t.NETWORK),e.hasStorage&&r.push(t.STORAGE),e.hasMessaging&&r.push(t.MESSAGING),e.hasDatabase&&r.push(t.DATABASE),e.hasCompute&&r.push(t.COMPUTE),e.hasCdn&&r.push(t.CDN),r.sort((c,O)=>{const A=S[c]-S[O];return o==="asc"?A:-A})}function x(e,o){return`${E(e)}${o}`}function k(e){switch(e){case"organisation":return"Organisation";case"platform":return"Platform";case"account":return"Account";default:{const o=e;throw new Error(`Unsupported organisation type: ${String(o)}`)}}}const D=new Set(Object.values(t));function m(e){return D.has(e)}function y(e,o){return`${o==="deploy"?"Deploying":"Destroying"} ${e.toLowerCase()} infrastructure`}const R={[t.NETWORK]:{deploy:s.NETWORK,destroy:s.NETWORK_DESTROY},[t.DATABASE]:{deploy:s.DATABASE,destroy:s.DATABASE_DESTROY},[t.STORAGE]:{deploy:s.STORAGE,destroy:s.STORAGE_DESTROY},[t.COMPUTE]:{deploy:s.COMPUTE,destroy:s.COMPUTE_DESTROY},[t.CDN]:{deploy:s.CDN,destroy:s.CDN_DESTROY},[t.MESSAGING]:{deploy:s.MESSAGING,destroy:s.MESSAGING_DESTROY}};function U(e,o){return R[e][o]}export{T as APPLICATION_DEPLOY_ORDER,u as APPLICATION_DESTROY_ORDER,t as APPLICATION_STACKS,P as OPENNEXT_DEPLOY_ORDER,N as OPENNEXT_DESTROY_ORDER,f as OPENNEXT_PARALLEL_GROUPS,C as ORGANISATION_TYPES,n as PARALLEL_DEPLOY_GROUPS,a as PARALLEL_DESTROY_GROUPS,M as getApplicationDeployOrder,h as getApplicationDestroyOrder,x as getApplicationStackName,U as getApplicationStepId,y as getApplicationStepName,k as getOrganisationStackName,I as getParallelDeployGroups,L as getParallelDestroyGroups,G as isApplicationOperation,m as isApplicationStack,g as isOrganisationOperation,E as toPascalCase};
@@ -2,7 +2,7 @@ import type { Result } from "@fjall/generator";
2
2
  import type { AwsCredentials, DeployIdentity } from "./credentials.js";
3
3
  import type { DeployCallbacks } from "./callbacks.js";
4
4
  import type { ApiClientInterface } from "./apiClient.js";
5
- import type { OrgConfig } from "./orgConfig.js";
5
+ import type { OrgConfig } from "./config/orgConfig.js";
6
6
  import type { DockerProvider } from "../orchestration/dockerInterface.js";
7
7
  import type { DomainDeployProvider } from "../orchestration/domainInterface.js";
8
8
  export interface DeployParams {
@@ -7,7 +7,7 @@
7
7
  * in steps/stepRegistry.ts — this file is the data layer.
8
8
  */
9
9
  import type { PatternType } from "@fjall/generator";
10
- import type { AppResourceFlags } from "./patternTypes.js";
10
+ import type { AppResourceFlags } from "./detection/patternTypes.js";
11
11
  /**
12
12
  * Step IDs used throughout the deployment process.
13
13
  * Centralised here to avoid magic strings and enable type safety.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/deploy-core",
3
- "version": "2.19.3",
3
+ "version": "2.19.5",
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",
@@ -78,15 +78,16 @@
78
78
  "@aws-sdk/client-sqs": "^3.1067.0",
79
79
  "@aws-sdk/client-sso-admin": "^3.1038.0",
80
80
  "@aws-sdk/client-sts": "^3.1038.0",
81
- "@fjall/generator": "^2.19.3",
82
- "@fjall/util": "^2.19.3",
81
+ "@fjall/generator": "^2.19.5",
82
+ "@fjall/util": "^2.19.5",
83
83
  "@smithy/node-http-handler": "^4.6.1",
84
84
  "tsx": "^4.21.0",
85
85
  "zod": "^4.4.3"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@types/node": "^25.6.0",
89
+ "typescript": "^6.0.3",
89
90
  "vitest": "^4.1.5"
90
91
  },
91
- "gitHead": "622bf104895ff3f51147de46d5b70c5928aaaa86"
92
+ "gitHead": "8179898f55c1467404e6a715ead5404404e45be0"
92
93
  }
@@ -1 +0,0 @@
1
- import{success as f,failure as c}from"@fjall/generator";import{maskSensitiveOutput as C,getErrorMessage as m}from"@fjall/util";import{ConfigError as u}from"../types/errors/ServiceError.js";const a="infrastructure.ts",O=["security","suspended"],g=new Set(O);function x(n){return g.has(n)}function T(n){return Array.isArray(n)&&n.every(t=>typeof t=="string")}function p(n){return typeof n!="object"||n===null?!1:Object.entries(n).every(([t,e])=>typeof t=="string"&&(typeof e=="string"||T(e)||p(e)))}function U(n){const t=[];for(const[e,r]of Object.entries(n))if(typeof r=="string")t.push({accountName:r,environment:e});else if(Array.isArray(r))for(const o of r)t.push({accountName:o,environment:e});else typeof r=="object"&&r!==null&&t.push(...U(r));return t}function N(n){const t=[];for(const e of Object.values(n))typeof e=="string"?t.push(e):Array.isArray(e)?t.push(...e):typeof e=="object"&&e!==null&&t.push(...N(e));return t}function d(n){const t={};for(const[e,r]of Object.entries(n))typeof r=="string"?t[e]=[r]:Array.isArray(r)?t[e]=[...r]:typeof r=="object"&&r!==null&&(t[e]=d(r));return t}async function E(n){try{const t=await import("path"),e=await import("fs"),{pathToFileURL:r}=await import("url"),o=t.join(n,"fjall","organisation",a);if(!e.existsSync(o))return f(null);const{tsImport:l}=await import("tsx/esm/api"),y=r(o).href,s=await l(o,y),A=s.default,i=s.ACCOUNTS||A?.ACCOUNTS;return i?p(i)?f(i):c(new u("ACCOUNTS configuration has invalid structure","ACCOUNTS",o,{providedValue:i})):c(new u(`ACCOUNTS constant not found in organisation/${a}`,"ACCOUNTS",o,{moduleKeys:Object.keys(s),hasDefault:s.default!==void 0}))}catch(t){const e=C(m(t));return c(new u(`Failed to import ACCOUNTS from organisation/${a}: ${e}`,"ACCOUNTS",void 0,{originalError:t}))}}export{O as OU_ONLY_ACCOUNT_BUCKETS,d as accountsConfigToOUTree,N as extractAllAccountNames,U as flattenAccountsToEnvironments,p as isAccountsConfig,x as isOuOnlyAccountBucket,T as isStringArray,E as parseAccountsConfiguration};
@@ -1 +0,0 @@
1
- import{join as $}from"path";import{CloudFormationClient as q}from"@aws-sdk/client-cloudformation";import{EC2Client as z}from"@aws-sdk/client-ec2";import{success as U,failure as g}from"@fjall/generator";import{logger as E}from"@fjall/util/logger";import{imageTagParameterName as Y,maskSensitiveOutput as S}from"@fjall/util";import{parseDockerServicesFromManifest as J}from"@fjall/util/manifest";import{stubCallerIdentity as Q}from"../types/deployment/index.js";import{getApplicationDeployOrder as V,getApplicationStackName as X,getApplicationStepName as F,getApplicationStepId as H,APPLICATION_STACKS as j}from"../types/operations.js";import{CdkContextBuilder as Z}from"../services/supporting/CdkContextBuilder.js";import{buildParamsContext as ee,bootstrapOrFail as te}from"./contextHelpers.js";import{runDetectionPipeline as oe}from"./detectionPipeline.js";import{STEP_IDS as O,STEP_NAMES as re}from"../types/stepDefinitions.js";import{StepRegistry as ne}from"../steps/stepRegistry.js";import{checkTargetReadiness as ae}from"../aws/targetReadiness.js";import{cascadeHomeRegion as ie}from"./cascadeHelpers.js";import{withStepLifecycle as se}from"./stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as G,runDockerBuild as ce}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as de,deployParallelPhase as le,deployStackSequential as pe,runDockerPreCompute as ue,createBuildCallbacks as fe}from"./applicationDeployHelpers.js";import{deployCodeOnly as me}from"./codeOnlyDeploy.js";const R="applicationDeploy";function ge(i){const a=Object.entries(i);if(a.length===0)return;const r={};for(const[t,s]of a){const C=Y(t);r[C]=s}return r}async function Me(i,a,r){const{callbacks:t,options:s}=i,C=Date.now(),D=Z.buildDeploymentContext({deployType:"application",target:r.appName,path:r.path,region:a.awsProvider.getRegion(),callerIdentity:Q(a.awsProvider.getAccountId()),...ee({orgConfig:i.orgConfig,identity:i.identity,skipOidc:i.options?.skipOidc})},{verbose:s?.verbose,infraOnly:s?.infraOnly},i.orgConfig),p=a.frameworkRegistry.resolve({appPath:r.path});let w;const k=!!s?.imageTag;if(p&&!k){w=p.builder.plan({appPath:r.path},p.detection);const e=fe(t),o=await p.builder.build(r.path,w,e,{skipBuild:s?.skipBuild,infraOnly:s?.infraOnly});if(!o.success){const n=new Error(S(o.error.message));return t.onError?.(n),g(n)}}if(s?.deployOnly||k){t.onLog?.(k?`Rollback mode \u2014 rolling to image tag ${s?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const e=$(r.path,"cdk.out"),o=J(e),n=o.length>0,l=p?.detection.hasDockerfile===!0,d=n||l;E.debug(R,"Deploy-only branch entered",{isRollback:k,imageTag:s?.imageTag,appName:r.appName,appPath:r.path,cdkOutPath:e,dockerProviderAvailable:i.dockerProvider!==void 0,builderName:p?.builder.name,hasDockerfileFromManifest:n,hasDockerfileFromDisk:l,hasDockerfile:d,manifestDockerServiceCount:o.length,manifestDockerPaths:o.map(u=>u.docker.path)}),!n&&!l&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");let f={};if(!k&&i.dockerProvider!==void 0&&d){E.debug(R,"Running Docker build before code-only deploy",{source:n?"manifest":"disk"});const u=await ce(i,a,r,t);if(!u.success)return g(u.error);f=u.data}else E.debug(R,"Skipping Docker build",{reason:k?"rollback":i.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return me(i,a,r,f)}t.onLog?.("Analysing infrastructure\u2026","info");const N=await oe(r,a,D,t);if(!N.success){const e=new Error(S(N.error.message));return t.onError?.(e),g(e)}const c=N.data;try{await t.onDetectionComplete?.({...c,builderName:p?.builder.name??"unknown"})}catch(e){const o=e instanceof Error?e.message:String(e),n=new Error(S(o));return t.onError?.(n),g(n)}const K={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:s?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...p&&{builderName:p.builder.name}},T=ne.getSteps(K),A=T.findIndex(e=>e.id===O.TARGET_READINESS),I=await se(t,{stepId:O.TARGET_READINESS,stepName:re.TARGET_READINESS,...A>=0&&{stepIndex:A,totalSteps:T.length}},async()=>{if(s?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const e=a.awsProvider.getAccountId(),o=i.orgConfig?.providerAccounts.find(f=>f.id===e)?.name,n=a.awsProvider.getCredentials(),l=ie(i.orgConfig),d=await ae({cloudFormation:a.awsProvider.getClient(q),ec2:new z({region:l,...n!==void 0&&{credentials:n}})},{id:e,...o!==void 0&&{name:o}},a.awsProvider.getRegion(),i.orgConfig,i.abortSignal);return d.success?d.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(S(d.data.advisory))}:{kind:"error",error:new Error(S(d.error.message))}});if(!I.success)return I;const m=w?w.deployOrder:V({pattern:c.pattern,resources:c.resources}),h=m.length;if(!c.hasDifferences&&!s?.force){t.onLog?.("No infrastructure changes detected","info");const e=c.hasDockerfile&&i.dockerProvider!==void 0&&m.includes(j.COMPUTE);for(let n=0;n<m.length;n++){const l=m[n];e&&l===j.COMPUTE&&(t.onStepStart?.(O.DOCKER_OPERATIONS,G),t.onStepComplete?.(O.DOCKER_OPERATIONS,G,"skipped"));const d=H(l,"deploy"),f=F(l,"deploy");t.onStepStart?.(d,f,n,h),t.onStepComplete?.(d,f,"skipped",n,h)}const o=await a.stackService.resolveWebsiteUrl(r.appName);return U({target:r.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,durationMs:Date.now()-C})}const v=await te(a,D,t);if(!v.success)return v;const b={},P=new Map;for(let e=0;e<m.length;e++){const o=m[e],n=X(r.appName,o),l=H(o,"deploy"),d=F(o,"deploy");if(!(c.stackChanges.get(n)??!0)&&!s?.force){t.onStepStart?.(l,d,e,h),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(l,d,"skipped",e,h);continue}const u=de(m,e,c.stackChanges,r.appName,s?.force);if(u.length>=2){const B=await le(u,r,a,D,t,e,h,c,b,P);if(!B.success)return g(B.error);e+=u.length-1;continue}const y=await ue(o,i,a,r,t,c.hasDockerfile);if(y!==null&&!y.success)return g(y.error);const W=y!==null&&y.success?y.data.contentHashTagsByService:{},L=ge(W),x=await pe(o,a,D,t,e,h,b,L!==void 0?{parameters:L}:void 0);if(!x.success)return g(x.error);const M=c.currentHashes.get(n);M&&P.set(n,M)}if(P.size>0){const e=await a.hashService.updateStateAfterDeploy(r.path,P);if(!e.success){const o=S(e.error.message);E.debug(R,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const _=await a.stackService.resolveWebsiteUrl(r.appName);return _&&(b.websiteUrl=_),U({target:r.appName,deploymentType:"application",outputs:Object.keys(b).length>0?b:void 0,durationMs:Date.now()-C})}export{Me as deployApplication};
@@ -1 +0,0 @@
1
- import{success as w,failure as b}from"@fjall/generator";import{getErrorMessage as y}from"@fjall/util";import{logger as S}from"@fjall/util/logger";import{getApplicationDestroyOrder as N,getApplicationStepName as h,getApplicationStepId as P}from"../types/operations.js";import{stubCallerIdentity as x}from"../types/deployment/index.js";import{deriveResourcesFromManifestStacks as D}from"../types/patternDetection.js";import{CdkContextBuilder as R}from"../services/supporting/CdkContextBuilder.js";import{buildParamsContext as I}from"./contextHelpers.js";import{deleteStateFile as L,readStateFile as O}from"../types/FjallState.js";async function q(n,c,r){const{callbacks:t}=n,C=Date.now(),m=c.frameworkRegistry.resolve({appPath:r.path})?.detection.pattern??null;let p;try{const e=await O(r.path);if(e!==null){const o=Object.keys(e.templateHashes);o.length>0&&(p=D(o))}}catch(e){S.debug("applicationDestroy","Could not read state file for resource detection",{error:y(e)}),t.onLog?.("Could not read state file for resource detection \u2014 falling back to pattern detection","warn")}const k=R.buildDeploymentContext({deployType:"application",target:r.appName,path:r.path,region:c.awsProvider.getRegion(),callerIdentity:x(c.awsProvider.getAccountId()),...I({orgConfig:n.orgConfig,identity:n.identity})},{verbose:n.options?.verbose},n.orgConfig),d=N({pattern:m,resources:p}),s=d.length;t.onLog?.(`Destroying ${r.appName} (${s} stacks, ${m??"standard"} pattern)`,"info");const f=[],u=[],g=await c.stackService.destroyAllStacks(k,{onOutput:e=>{t.onOutput?.(e)},onLog:t.onLog,onStackCleanupProgress:t.onStackCleanupProgress,onResourceProgress:(e,o)=>{t.onResourceProgress?.(e),o&&t.onParallelStackResourceProgress?.(o,e)},onStackStart:(e,o)=>{const a=P(e,"destroy"),l=h(e,"destroy"),i=d.indexOf(e);t.onStepStart?.(a,l,i,s)},onStackComplete:async(e,o)=>{const a=P(e,"destroy"),l=h(e,"destroy"),i=d.indexOf(e);o.success?o.data?.skipped?(u.push(o.data.stackName||e),t.onStepComplete?.(a,l,"skipped",i,s)):(f.push(o.data?.stackName||e),t.onStepComplete?.(a,l,"completed",i,s)):(t.onStepComplete?.(a,l,"error",i,s),t.onError?.(o.error))},onParallelPhaseStart:(e,o)=>{t.onLog?.(`Parallel phase: ${o}`,"info"),t.onParallelPhaseStart?.(e,o)},onParallelPhaseComplete:e=>{const o=e.filter(a=>!a.success);o.length>0&&t.onLog?.(`Parallel phase completed with ${o.length} failure(s)`,"warn"),t.onParallelPhaseComplete?.(e)}},p,n.abortSignal);if(!g.success)return t.onError?.(g.error),b(g.error);try{await L(r.path)}catch(e){S.debug("applicationDestroy","Failed to delete state file (non-critical)",{error:y(e)}),t.onLog?.("Failed to delete state file (non-critical)","warn")}return w({target:r.appName,deploymentType:"application",stacksDestroyed:f,skippedStacks:u,durationMs:Date.now()-C})}export{q as destroyApplication};
@@ -1 +0,0 @@
1
- import{join as B}from"path";import{logger as f}from"@fjall/util/logger";import{maskSensitiveOutput as i,getErrorMessage as g}from"@fjall/util";import{stubCallerIdentity as I}from"../types/deployment/index.js";import{CloudFormationClient as P,DescribeStacksCommand as _}from"@aws-sdk/client-cloudformation";import{S3Client as v}from"@aws-sdk/client-s3";import{BackupClient as x}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as F,describeSurvivingBackupVault as M,formatSurvivingVaultWarning as K}from"../aws/organisations/backup.js";import{composeSdkAbortSignal as L}from"../aws/organisations/types.js";import{ORGANISATION_TYPES as R,getOrganisationStackName as V}from"../types/operations.js";import{CdkContextBuilder as j}from"../services/supporting/CdkContextBuilder.js";import{regionSuffix as U}from"../types/FjallState.js";import{buildParamsContext as G,assumeCascadeRole as H,forwardOutput as E}from"./contextHelpers.js";import{cascadeOperationKey as W}from"./cascadeHelpers.js";import{capturedSweepBuckets as Y,cleanupFailedStack as q,preEmptyStackBuckets as z,sweepOrphanedDestroyBuckets as N}from"./stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as J,STACK_FAILED_STATE_PATTERN as Q}from"../types/constants.js";async function Se(a,n,c,e,t,r,o,T){const S=Date.now(),u=W(e.name,r),l=(s,p=s)=>(o.onCascadeAccountComplete?.(u,!1,p,r),{accountName:e.name,accountId:e.id,region:r,success:!1,duration:Date.now()-S,error:s});o.onCascadeAccountStart?.(u,e.id,r,t);const y=await H(n.awsProvider,e.id,r,`fjall-cascade-destroy-${e.name}`);if(!y.success){const s=i(y.error.message);return l(`AssumeRole failed: ${s}`,s)}const{provider:d,credentials:w}=y.data,O=B(c.path,`cdk.out.${e.id}.${U(r)}`),k=j.buildDeploymentContext({deployType:t,target:c.target,path:c.path,assemblyDir:O,region:r,accountName:e.name,callerIdentity:I(e.id),...G({orgConfig:a.orgConfig,identity:a.identity,region:r,primaryRegion:T?.primaryRegion})},{verbose:a.options?.verbose},a.orgConfig);o.onCascadeAccountPhaseChange?.(u,"synth",r);const A=await n.cdkService.runCdkSynth(k,E(o));if(!A.success)return l(i(`Synth failed: ${A.error}`));o.onCascadeAccountPhaseChange?.(u,"destroy",r);const m=V(t==="platform"?R.PLATFORM:R.ACCOUNT);F(e.environment,a.orgConfig?.disasterRecoveryRegion)&&await X(d.getClient(x),r,o,a.abortSignal);const $=await z(d.getClient(P),d.getClient(v),m,o,a.abortSignal),C=Y($),D=await n.cdkService.runCdkDestroy(k,m,E(o),s=>o.onCascadeAccountResourceProgress?.(u,s,r),d,!0,w);if(!D.success){const s=D.error;if(s.includes(Q)){f.warn("cascadeDestroy",`CDK destroy failed on ${m} in failed state, retrying via CloudFormation API`,{region:r,account:e.name});try{await q(m,r,w,{accountId:e.id,abortSignal:a.abortSignal},o)}catch(b){const h=`cleanupFailedStack threw for ${m}: ${i(g(b))}`;f.warn("cascadeDestroy",h),o.onLog?.(h,"warn")}const p=await Z(m,d.getClient(P),a.abortSignal);return p.deleted?(C.length>0&&await N(d.getClient(v),C,o,a.abortSignal),o.onCascadeAccountComplete?.(u,!0,void 0,r),{accountName:e.name,accountId:e.id,region:r,success:!0,duration:Date.now()-S}):p.error?l(i(p.error)):l(i(`Stack ${m} cleanup attempted but stack still exists in ${r}`))}return l(i(s))}return C.length>0&&await N(d.getClient(v),C,o,a.abortSignal),o.onCascadeAccountComplete?.(u,!0,void 0,r),{accountName:e.name,accountId:e.id,region:r,success:!0,duration:Date.now()-S}}async function X(a,n,c,e){try{const t=await M(a,n,e);if(!t.success){f.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:i(t.error.message)});return}if(t.data===null)return;c.onProgress?.({type:"warning",message:K(t.data),metadata:{source:"backup-vault-survival"}}),f.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:t.data.vaultName,recoveryPointCount:t.data.recoveryPointCount,lockPermanent:t.data.lockPermanent})}catch(t){f.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:i(g(t))})}}async function Z(a,n,c){try{const t=(await n.send(new _({StackName:a}),{abortSignal:L(c)})).Stacks?.[0]?.StackStatus;return!t||t==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${t} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(J))return{deleted:!0};const t=i(g(e));return f.debug("cascadeDestroy","Stack verification failed",{error:t}),{deleted:!1,error:`Stack verification failed: ${t}`}}}export{Se as destroyCascadeAccount};
@@ -1 +0,0 @@
1
- import{join as L}from"path";import{success as B,failure as h}from"@fjall/generator";import{logger as y}from"@fjall/util/logger";import{maskSensitiveOutput as p,mapSettledWithConcurrency as U}from"@fjall/util";import{ORGANISATION_TYPES as R,getOrganisationStackName as j}from"../types/operations.js";import{CdkContextBuilder as _}from"../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as K}from"../types/deployment/index.js";import{CloudFormationService as H}from"../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as z,regionSuffix as G}from"../types/FjallState.js";import{BackupClient as Y}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as W,describeBackupVaultExists as X}from"../aws/organisations/backup.js";import{buildParamsContext as q,collectStackOutputs as M,assumeCascadeRole as v,forwardOutput as x}from"./contextHelpers.js";import{accountTier as T}from"@fjall/util";import{DEFAULT_REGION as b}from"../aws/utils/regions.js";const J=4;function le(o){const n=o.find(e=>T(e)==="platform"),c=o.filter(e=>T(e)==="account");return{platformAccount:n,memberAccounts:c}}function Q(o){const n=o?.primaryRegion??b,c=o?.secondaryRegions??[],e=new Set([n,...c]),i=o?.disasterRecoveryRegion;return i&&e.add(i),[...e]}function ge(o){return Q(o)[0]??b}function Z(o,n){return`${o} (${n})`}function Ce(o,n){const c=[];for(const e of n)for(const i of o)c.push({account:i,region:e});return c}import{buildCascadeRoleArn as $e}from"./contextHelpers.js";async function he(o,n,c,e){c.onLog?.(`Verifying cascade role access for ${n.length} account(s)\u2026`,"info");const i=await U(n,J,async a=>v(o.awsProvider,a.id,a.region??o.awsProvider.getRegion(),`fjall-preflight-${a.name}`,e)),s=[];return i.forEach((a,t)=>{const r=n[t];if(r){if(a.status==="rejected"){s.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||s.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),s.length===0&&c.onLog?.(`Cascade role access verified for ${n.length} account(s)`,"info"),s}async function ye(o,n,c,e,i,s,a){const t=a?.region??e.region??n.awsProvider.getRegion(),r=Z(e.name,t),u=a?.orgConfig??o.orgConfig,l=a?.ipamPoolId;s.onCascadeAccountStart?.(r,e.id,t,i);const d=await v(n.awsProvider,e.id,t,`fjall-cascade-${e.name}`,o.abortSignal);if(!d.success)return s.onCascadeAccountComplete?.(r,!1,p(d.error.message),t),h(new Error(`Failed to assume role for ${e.name}: ${p(d.error.message)}`));const{provider:g,credentials:C}=d.data,P=L(o.workingDirectory,"fjall",i==="platform"?R.PLATFORM:R.ACCOUNT),D=L(P,`cdk.out.${e.id}.${G(t)}`),w=_.buildDeploymentContext({deployType:i,target:c.target,path:P,assemblyDir:D,environment:e.environment??void 0,region:t,accountName:e.name,callerIdentity:K(e.id),ipamPoolId:l,...q({orgConfig:u,identity:o.identity,skipOidc:o.options?.skipOidc,region:t,primaryRegion:a?.primaryRegion,trailLifecycle:e.trailLifecycle})},{verbose:o.options?.verbose},u);if(W(e.environment,u?.disasterRecoveryRegion)){const f=await X(g.getClient(Y),o.abortSignal);if(!f.success)return s.onCascadeAccountComplete?.(r,!1,p(f.error.message),t),h(new Error(`Backup vault probe failed for ${e.name}: ${p(f.error.message)}`));w.fjallAdoptBackupVault=f.data}s.onCascadeAccountPhaseChange?.(r,"synth",t);const S=await n.cdkService.runCdkSynth(w,x(s),C);if(!S.success)return s.onCascadeAccountComplete?.(r,!1,p(`Synth failed: ${S.error}`),t),h(new Error(`Synth failed for ${e.name}: ${p(S.error)}`));const m=j(i==="platform"?R.PLATFORM:R.ACCOUNT),k=z(P,e.id,t),$=new H(g),{changed:V,currentHash:I}=await n.hashService.compareCascadeStack(D,m,k);if(!V&&o.options?.force!==!0&&await $.stackExists(m)){const f=await $.getStackOutputs(m);f.success||y.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:m,account:e.name});const F=M(f);return s.onLog?.(`${e.name}: no infrastructure changes \u2014 skipping deploy`,"info"),s.onCascadeAccountComplete?.(r,!0,void 0,t,F,!0),B({outputs:F,skipped:!0})}s.onCascadeAccountPhaseChange?.(r,"bootstrap",t);const A=await n.cdkService.runCdkBootstrap(w,x(s),C);if(!A.success)return s.onCascadeAccountComplete?.(r,!1,p(`Bootstrap failed: ${A.error}`),t),h(new Error(`Bootstrap failed for ${e.name}: ${p(A.error)}`));s.onCascadeAccountPhaseChange?.(r,"deploy",t);const O=await n.cdkService.runCdkDeploy(w,m,x(s),f=>s.onCascadeAccountResourceProgress?.(r,f,t),g,C);if(!O.success)return s.onCascadeAccountComplete?.(r,!1,p(O.error),t),h(new Error(p(O.error)));const E=await $.getStackOutputs(m);E.success||y.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:m,account:e.name});const N=M(E);return I!==void 0&&((await n.hashService.persistCascadeStack(k,m,I)).success||y.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:m,account:e.name})),s.onCascadeAccountComplete?.(r,!0,void 0,t,N,!1),B({outputs:N,skipped:!1})}async function Re(o,n,c,e){const i=new Map,s=o.awsProvider.getRegion(),a=await v(o.awsProvider,n.id,s,`fjall-ipam-read-${n.name}`,e);if(!a.success)return y.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const t=new H(a.data.provider),r=j(R.PLATFORM),u=await t.getStackOutputs(r);if(!u.success)return y.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const l=/^IpamPoolId(\d{12})(\w+)$/;for(const d of u.data){const g=d.OutputKey?.match(l);if(g&&d.OutputValue){const C=`${g[1]}-${g[2]}`;i.set(C,d.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}async function we(o,n){const c=o.getDomains();if(c.length===0)return{domainsDeployed:0,errors:[]};n.onCascadePhaseStart?.("domains");const e=c.filter(t=>t.type==="apex"),i=c.filter(t=>t.type==="delegated");let s=0;const a=[];for(const t of e){const r=await o.deployDomain(t.name,n);r.success?s++:a.push(`${t.name}: ${r.error.message}`)}if(i.length>0){const t=await Promise.allSettled(i.map(r=>o.deployDomain(r.name,n)));for(let r=0;r<t.length;r++){const u=t[r],l=i[r];if(!(!u||!l))if(u.status==="fulfilled")u.value.success?s++:a.push(`${l.name}: ${u.value.error.message}`);else{const d=u.reason instanceof Error?u.reason.message:String(u.reason);a.push(`${l.name}: ${d}`)}}}return n.onCascadePhaseComplete?.("domains"),{domainsDeployed:s,errors:a}}export{J as CASCADE_MAX_CONCURRENCY,Ce as buildAccountRegionPairs,$e as buildCascadeRoleArn,Q as buildRegionList,ge as cascadeHomeRegion,Z as cascadeOperationKey,ye as deployCascadeAccount,we as deployDomains,le as partitionAccounts,he as probeCascadeRoles,Re as readPlatformIpamPoolIds};
@@ -1 +0,0 @@
1
- import{join as U}from"path";import{success as _,failure as k}from"@fjall/generator";import{logger as L}from"@fjall/util/logger";import{maskSensitiveOutput as E}from"@fjall/util";import{parseDockerServicesFromManifest as j}from"@fjall/util/manifest";import{APPLICATION_STACKS as P,getApplicationStepId as N,getApplicationStepName as W}from"../types/operations.js";import{extractEcsServiceName as O}from"../aws/utils/arnParser.js";import{stripTagOrDigest as F}from"../services/infrastructure/EcrImageInspectorService.js";import{withStepLifecycle as q}from"./stepLifecycle.js";const I="codeOnlyDeploy";function z(n,a){if(!n)return;const o=n.docker.target,e=o?`${n.name}-${o}`:n.name;return a[e]}function G(n,a){const o=a.toLowerCase();return n.find(e=>e.name.toLowerCase()===o)??n.find(e=>o.includes(e.name.toLowerCase()))}function H(n,a,o){const e=a.toLowerCase(),c=n.toLowerCase(),g=o.map(m=>m.toLowerCase()).filter(m=>m!==""&&c.startsWith(`${m}-`)).sort((m,T)=>T.length-m.length)[0];if(g===void 0||g===e)return n;const v=n.slice(g.length);return`${e}${v}`}const K=0,X=1;async function se(n,a,o,e){const{callbacks:c,options:g}=n,v=Date.now(),m=N(P.COMPUTE,"deploy"),T=W(P.COMPUTE,"deploy");return q(c,{stepId:m,stepName:T,stepIndex:K,totalSteps:X},async()=>{const d=g?.imageTag;d&&c.onLog?.(`Rolling to supplied image tag ${d}`,"info");const S=j(U(o.path,"cdk.out"));L.debug(I,"Code-only rollout starting",{explicitRollbackTag:d,workingDirectory:n.workingDirectory,contentHashTagsByService:e,manifestServiceCount:S.length,manifestServices:S.map(t=>({name:t.name,target:t.docker.target}))}),c.onLog?.("Discovering ECS cluster and services\u2026","info");const D=await a.ecsResolver.getDeployableClusterAndServices(o.appName);if(!D.success)return L.debug(I,"ECS discovery failed",{appName:o.appName,error:E(D.error.message)}),{kind:"error",error:D.error};const{clusterArn:r,serviceArns:$}=D.data;if(L.debug(I,"ECS discovery complete",{appName:o.appName,clusterArn:r,serviceCount:$.length,serviceArns:$}),!r||$.length===0){const t=`No deployable ECS services found for ${o.appName}`;return c.onLog?.(t,"warn"),{kind:"skipped",data:{target:o.appName,deploymentType:"application",durationMs:Date.now()-v,noChanges:!0}}}const b=g?.serviceName?.toLowerCase(),w=b===""?void 0:b,f=w?$.filter(t=>{const l=(O(t)??"").toLowerCase();return l===w||l.endsWith(w)}):$;if(w&&f.length===0){const t=$.map(l=>O(l)??"unknown").join(", ");return{kind:"error",error:new Error(`Service '${w}' not found. Available: ${t||"none"}`)}}const C={},h=[],R=$.map(t=>O(t)).filter(t=>t!==void 0&&t!=="");for(let t=0;t<f.length;t++){const l=f[t];if(!l)continue;const i=O(l)??`service-${t+1}`,A=`[${t+1}/${f.length}] ${i}`,y=G(S,i),x=z(y,e);let p,s;d!==void 0?(p=H(d,i,R),p!==d?(s=d,c.onLog?.(`Re-based image tag ${d} \u2192 ${p} for ${i}`,"info")):R.length>1&&!d.toLowerCase().startsWith(`${i.toLowerCase()}-`)&&c.onLog?.(`Image tag ${d} has no recognised service prefix; applying verbatim to ${i} \u2014 confirm it points at this service's image`,"warn")):x!==void 0?p=x:(p=`${i.toLowerCase()}-latest`,c.onLog?.(`No content-hash tag found for ${i}; falling back to mutable tag ${p}`,"warn")),L.debug(I,"Starting service rollout",{serviceName:i,serviceArn:l,imageTag:p,explicitRollbackTag:d,contentHashTag:x,manifestServiceMatched:y?.name,dockerTarget:y?.docker.target,index:t,total:f.length}),c.onECSUpdate?.(`${A}: rolling out image tag ${p}`,i);const u=await Y(a,r,l,i,p,c,s);if(L.debug(I,"Service rollout completed",{serviceName:i,success:u.success,...u.success?{taskDefinitionArn:u.data.taskDefinitionArn,imageDigest:u.data.imageDigest,durationMs:u.data.durationMs}:{error:E(u.error.message)}}),!u.success){h.push(`${i}: ${E(u.error.message)}`);continue}C[`${i}TaskDefinition`]=u.data.taskDefinitionArn,C[`${i}PreviousTaskDefinition`]=u.data.previousTaskDefinitionArn,C[`${i}ImageTag`]=p,C[`${i}ImageUri`]=u.data.imageUri,C[`${i}EcrRepositoryArn`]=u.data.ecrRepositoryArn,u.data.imageDigest!==void 0&&(C[`${i}ImageDigest`]=u.data.imageDigest)}const M=Date.now()-v;if(h.length>0){const t=f.length-h.length,l=f.length,i=h.length===l?`All ${l} ECS service rollouts failed: ${h.join("; ")}`:`Partial rollout: ${t}/${l} services succeeded. Failures: ${h.join("; ")}`;return{kind:"error",error:new Error(i)}}return{kind:"completed",data:{target:o.appName,deploymentType:"application",outputs:Object.keys(C).length>0?C:void 0,durationMs:M}}})}async function Y(n,a,o,e,c,g,v){const m=Date.now(),T=await n.ecsService.describeServices(a,[o]);if(!T.success)return k(new Error(`Failed to describe service ${e}: ${T.error.message}`));const d=T.data[0];if(!d?.taskDefinition)return k(new Error(`Service ${e} has no task definition`));const S=d.taskDefinition,D=await n.ecsService.getLatestTaskDefinition(S);if(!D.success)return k(new Error(`Failed to fetch task definition for ${e}: ${D.error.message}`));const r=D.data;if(!r||!r.containerDefinitions||r.containerDefinitions.length===0)return k(new Error(`Task definition for ${e} has no container definitions`));const $=r.containerDefinitions.find(s=>s.image)?.image;if(!$)return k(new Error(`Task definition for ${e} has no container image \u2014 cannot derive repository URI`));const b=F($),w=await n.ecrImageInspector.getImageDigest(b,c);let f;if(w.success)f=w.data;else{if(v!==void 0)return k(new Error(`Re-based image tag '${c}' (from supplied '${v}') was not found in ECR for ${e}: ${E(w.error.message)}. The supplied tag's build has no image for ${e} \u2014 supply that service's own tag, or pass --service to roll a single service.`));g.onLog?.(`Could not resolve image digest for ${e} (${E(w.error.message)}); falling back to tag pinning`,"warn")}const C=s=>f!==void 0?`${s}@${f}`:`${s}:${c}`,h=r.containerDefinitions.map(s=>s.image?{...s,image:C(F(s.image))}:s),R=r.family;if(!R)return k(new Error(`Task definition for ${e} has no family`));const M=await n.ecsService.registerTaskDefinition({family:R,taskRoleArn:r.taskRoleArn,executionRoleArn:r.executionRoleArn,networkMode:r.networkMode,containerDefinitions:h,volumes:r.volumes,placementConstraints:r.placementConstraints,requiresCompatibilities:r.requiresCompatibilities,cpu:r.cpu,memory:r.memory,runtimePlatform:r.runtimePlatform,proxyConfiguration:r.proxyConfiguration,inferenceAccelerators:r.inferenceAccelerators,pidMode:r.pidMode,ipcMode:r.ipcMode,ephemeralStorage:r.ephemeralStorage});if(!M.success)return k(new Error(`Failed to register task definition for ${e}: ${M.error.message}`));const t=M.data;g.onTaskDefRegistered?.({serviceName:e,taskDefinitionArn:t,previousTaskDefinitionArn:S,revision:Q(t),family:R,...f!==void 0?{imageDigest:f}:{}});const l=await n.ecsService.updateService(a,o,{taskDefinition:t,forceNewDeployment:!0});if(!l.success)return k(new Error(`Failed to update service ${e}: ${l.error.message}`));const i=await n.ecsService.pollDeployment({clusterArn:a,serviceArn:o,waitForCompletion:!0,progressCallback:s=>{const u=s.latestEvent?` \u2014 ${E(s.latestEvent)}`:"";g.onECSProgress?.(`${e}: ${E(s.message)}${u}`,s.percentComplete)}}),A=Date.now()-m;if(!i.success){const s=E(i.error.message);return g.onECSComplete?.({serviceName:e,serviceArn:o,success:!1,taskDefinitionArn:t,durationMs:A,reason:s}),k(new Error(`ECS rollout failed for ${e}: ${s}`))}const y=await n.ecsService.getDeploymentStatus(a,o);g.onECSComplete?.({serviceName:e,serviceArn:o,success:!0,taskDefinitionArn:t,durationMs:A,finalRunningCount:y.success?y.data.runningCount:void 0,finalDesiredCount:y.success?y.data.desiredCount:void 0}),L.debug(I,`Rolled out ${e} successfully`,{serviceArn:o,newTaskDefArn:t,previousTaskDefArn:S,durationMs:A});const x=`${b}:${c}`,p=J(b);return _({taskDefinitionArn:t,previousTaskDefinitionArn:S,imageUri:x,ecrRepositoryArn:p,imageDigest:f,durationMs:A})}function J(n){const a=n.match(/^(\d{12})\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com\/(.+)$/);if(!a)return"";const[,o,e,c]=a;return`arn:aws:ecr:${e}:${o}:repository/${c}`}function Q(n){const a=n.lastIndexOf(":");if(a<0)return 0;const o=n.slice(a+1);return/^\d+$/.test(o)?parseInt(o,10):0}export{se as deployCodeOnly,H as rebaseRollbackTagForService};
@@ -1 +0,0 @@
1
- import{join as A}from"path";import{success as N,failure as n}from"@fjall/generator";import{logger as F}from"@fjall/util/logger";import{maskSensitiveOutput as o}from"@fjall/util";import{deriveResourcesFromManifestStacks as H}from"../types/patternDetection.js";import{emitProgress as P,PROGRESS_MESSAGES as w}from"../services/supporting/helpers.js";import{parseRequiredSecretsFromManifest as M,parseImportedSecretNamesFromManifest as O}from"./manifestSecretParser.js";import{resolveSecretArns as L}from"./secretArnResolver.js";import{parseDockerServicesFromManifest as q}from"@fjall/util/manifest";async function Y(h,r,a,e){e.onDetectionPhaseChange?.("detect","started");const S=r.frameworkRegistry.resolve({appPath:h.path}),C=S?.detection.pattern??null,l=S?.detection.hasDatabase??!1;e.onLog?.(`Pattern detected: ${C??"standard"}`,"info"),e.onDetectionPhaseChange?.("detect","completed");const i=A(h.path,"cdk.out");e.onDetectionPhaseChange?.("synth","started"),P(e,w.SYNTH);const y=await r.cdkService.runCdkSynth(a,t=>e.onCdkOutput?.(t,"synth"));if(!y.success)return n(new Error(o(`CDK synthesis failed: ${y.error}`)));if(e.onDetectionPhaseChange?.("synth","completed"),a.resolvedSecretArns===void 0){const t=O(i);if(t.length>0){e.onLog?.(`Resolving ${t.length} imported secret ARN(s)\u2026`,"info");const s=await L(r.awsProvider,t);if(!s.success)return n(new Error(o(s.error.message)));a.resolvedSecretArns=JSON.stringify(s.data);const g=await r.cdkService.runCdkSynth(a,$=>e.onCdkOutput?.($,"synth"));if(!g.success)return n(new Error(o(`CDK synthesis failed: ${g.error}`)))}}const d=M(i);d.length>0&&e.onLog?.(`Found ${d.length} required secret path(s) in manifest`,"info"),e.onDetectionPhaseChange?.("hash","started"),P(e,w.HASH);const m=await r.hashService.getTemplateHashes(i);if(!m.success)return n(new Error(o(`Template hash computation failed: ${m.error.message}`)));const u=m.data,f=await r.hashService.compareWithState(u,h.path);if(!f.success)return n(new Error(o(`Hash comparison failed: ${f.error.message}`)));const c=f.data;e.onDetectionPhaseChange?.("hash","completed");const p=new Map(c.stackChanges);for(const[t,s]of c.stackChanges)s||await r.cfnService.stackExists(t)||(F.debug("detectionPipeline","Stale hash detected \u2014 stack missing in CFN",{stackName:t}),p.set(t,!0));const R=Array.from(u.keys()),D=H(R),v=q(i),k=D.hasCompute&&v.length>0,E=Array.from(p.values()).some(Boolean);return e.onLog?.(`Detection complete: ${c.changedCount} changed, ${c.unchangedCount} unchanged`,"info"),N({pattern:C,hasDatabase:l,hasDifferences:E,stackChanges:p,currentHashes:u,resources:D,hasDockerfile:k,requiredSecrets:d})}export{Y as runDetectionPipeline};
@@ -1 +0,0 @@
1
- import{isAbsolute as $,join as N,dirname as T,resolve as E}from"node:path";import{existsSync as R}from"node:fs";import{success as b,failure as h}from"@fjall/generator";import{deriveContentHashTag as w,maskSensitiveOutput as k,toKebab as A}from"@fjall/util";import{logger as P}from"@fjall/util/logger";import{parseDockerServicesFromManifest as x}from"@fjall/util/manifest";import{STEP_IDS as C}from"../types/stepDefinitions.js";const S="dockerBuildHelper",v="Building and pushing Docker image";function I(e,n,r){const t=N(n,e);if(R(t))return{path:t,base:n};if(r!==""&&r!==n){const i=N(r,e);if(R(i))return{path:i,base:r}}return{path:t,base:n}}function L(e,n,r){const t=e.docker.path,i=e.docker.context;let o,s;if($(t))o=t,s=n;else{const a=I(t,n,r);o=a.path,s=a.base}return{buildContext:i?$(i)?E(i):E(s,i):E(T(o)),dockerfilePath:o,target:e.docker.target,resolvedViaFallback:s!==n}}function B(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function _(e,n){return`Dockerfile not found for service '${e}' at ${n}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function K(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function H(e,n,r){return`${e}.dkr.ecr.${n}.${K(n)}/${A(r)}`}function j(e,n){const r=n.name.toLowerCase(),t=n.docker.target,i=t?`-${t.toLowerCase()}`:"";return[`${e}:${r}${i}-latest`]}function F(e,n,r,t){const i=new Map;for(const o of e){const s=L(o,n,t),u=B(s),a=j(r,o),d=i.get(u);d?(d.members.push(o),d.latestTags.push(...a)):i.set(u,{identity:s,members:[o],latestTags:[...a]})}return Array.from(i.values())}async function U(e,n,r,t,i){const o=await e.buildAndPush({appName:n.appName,appPath:n.path,region:r,accountId:t},(s,u,a,d)=>{i.onDockerProgress?.(k(s),u,a,d)});return o.success?b({imageUri:o.data.imageUri,imageTag:o.data.imageTag}):h(o.error)}async function M(e,n,r,t,i,o,s){const u=e.members[0];if(!R(e.identity.dockerfilePath))return h(new Error(_(u.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&s.onLog?.(`Building service '${u.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const a={appName:r.appName,appPath:e.identity.buildContext,region:t,accountId:i,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,imageTags:e.latestTags,serviceName:u.name,...e.identity.target!==void 0&&{target:e.identity.target},...u.docker.buildArgs!==void 0&&{buildArgs:u.docker.buildArgs}},d=await n.buildAndPush(a,(p,l,g,D)=>{s.onDockerProgress?.(k(p),l,g,D)});if(!d.success)return h(d.error);const m=d.data.imageDigest;if(typeof m!="string"||!m.startsWith("sha256:"))return h(new Error(`Buildx push succeeded but returned an invalid digest: ${k(String(m))}`));const y={},c=[];for(const p of e.members){const l=p.docker.target,g=w(m,p.name,l);if(!g.success)return h(g.error);const D=g.data,O=p.name;y[O]=D,c.push(`${o}:${D}`)}const f=await n.tagByDigest({sourceImage:o,digest:m,tags:c});return f.success?b({contentHashTagsByService:y}):h(f.error)}async function Q(e,n,r,t){const i=e.dockerProvider;if(!i)return b({});const o=n.awsProvider.getAccountId();if(!o)return t.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),b({});const s=n.awsProvider.getRegion(),u=N(r.path,"cdk.out"),a=x(u),d=H(o,s,r.appName);if(P.debug(S,"runDockerBuild starting",{appName:r.appName,appPath:r.path,accountId:o,region:s,cdkOutPath:u,manifestServiceCount:a.length,manifestServices:a.map(c=>({name:c.name,path:c.docker.path,context:c.docker.context,target:c.docker.target})),stepId:C.DOCKER_OPERATIONS,stepName:v}),t.onStepStart?.(C.DOCKER_OPERATIONS,v),a.length===0){t.onLog?.("Initialising ECR repository\u2026","info");const c=await i.initialiseECR({appName:r.appName,region:s,accountId:o});c.success||t.onLog?.(`ECR initialisation failed: ${k(c.error.message)}`,"warn"),t.onLog?.("Building and pushing Docker image\u2026","info");const f=await U(i,r,s,o,t);if(!f.success){P.debug(S,"Docker buildAndPush (legacy) failed",{appName:r.appName,error:k(f.error.message)}),t.onStepComplete?.(C.DOCKER_OPERATIONS,v,"error");const p=new Error(k(f.error.message));return t.onError?.(p),h(p)}return t.onStepComplete?.(C.DOCKER_OPERATIONS,v,"completed"),t.onLog?.(`Docker image pushed: ${f.data.imageUri}`,"info"),b({})}const m=F(a,r.path,d,e.workingDirectory);t.onLog?.(`Building and pushing ${a.length} service image(s) in ${m.length} group(s)\u2026`,"info");const y={};for(let c=0;c<m.length;c++){const f=m[c],p=f.members.map(g=>g.name).join(", ");t.onLog?.(`Building group ${c+1}/${m.length} (${p})\u2026`,"info");const l=await M(f,i,r,s,o,d,t);if(!l.success){P.debug(S,"Docker buildAndPush failed",{appName:r.appName,leader:f.members[0]?.name,members:p,error:k(l.error.message)}),t.onStepComplete?.(C.DOCKER_OPERATIONS,v,"error");const g=new Error(k(l.error.message));return t.onError?.(g),h(g)}for(const[g,D]of Object.entries(l.data.contentHashTagsByService))y[g]=D}return P.debug(S,"Docker buildAndPush succeeded",{appName:r.appName,services:a.map(c=>c.name),groupCount:m.length,contentHashTags:y}),t.onStepComplete?.(C.DOCKER_OPERATIONS,v,"completed"),t.onLog?.(`Docker images pushed for ${a.length} service(s)`,"info"),b(y)}export{v as DOCKER_BUILD_STEP_NAME,Q as runDockerBuild};
@@ -1 +0,0 @@
1
- import{failure as a}from"@fjall/generator";import{ORGANISATION_TYPES as o}from"../types/operations.js";import{deploySingleComponent as i}from"./organisationDeploy/singleComponentDeploy.js";import{deployOrgWithCascade as c}from"./organisationDeploy/orgCascadeDeploy.js";async function l(r,e,t){const n=Date.now();switch(t.type){case o.ORGANISATION:return c(r,e,t,n);case o.PLATFORM:return i(r,e,t,"platform",n);case o.ACCOUNT:return i(r,e,t,"account",n);default:{const u=t.type;return a(new Error(`Unsupported organisation type: ${String(u)}`))}}}export{l as deployOrganisation};
@@ -1,3 +0,0 @@
1
- import{CloudFormationClient as k}from"@aws-sdk/client-cloudformation";import{S3Client as T}from"@aws-sdk/client-s3";import{success as D,failure as N}from"@fjall/generator";import{maskSensitiveOutput as O,getErrorMessage as M,mapSettledWithConcurrency as _}from"@fjall/util";import{stubCallerIdentity as b}from"../types/deployment/index.js";import{ORGANISATION_TYPES as v,getOrganisationStackName as L}from"../types/operations.js";import{CdkContextBuilder as x}from"../services/supporting/CdkContextBuilder.js";import{buildParamsContext as B,synthOrFail as G,forwardOutput as Y,forwardResourceProgress as j}from"./contextHelpers.js";import{destroyCascadeAccount as $}from"./cascadeDestroyHelpers.js";import{capturedSweepBuckets as F,preEmptyStackBuckets as U,sweepOrphanedDestroyBuckets as H}from"./stackCleanup.js";import{partitionAccounts as W,buildRegionList as X,buildAccountRegionPairs as q,cascadeHomeRegion as z,CASCADE_MAX_CONCURRENCY as J}from"./cascadeHelpers.js";import{projectScalarSummary as K}from"./cascadeSummary.js";import{STEP_IDS as Q}from"../types/stepDefinitions.js";const E=Q.ORG_DESTROY,I="Destroying organisation infrastructure";async function lt(o,r,c){const S=Date.now(),{callbacks:t}=o,l=o.orgConfig?.providerAccounts??[],g=z(o.orgConfig),m=X(o.orgConfig),{platformAccount:u,memberAccounts:f}=W(l),h=o.options?.cascade!==!1;t.onLog?.(`Destroying organisation infrastructure (${l.length} accounts, ${m.length} region(s))`,"info");const n=[],p=[],w=[];let R;if(h){t.onCascadeStart?.();const A=Date.now();if(f.length>0){t.onCascadePhaseStart?.("accounts");const a=q(f,m),d=await _(a,J,({account:s,region:i})=>$(o,r,c,s,"account",i,t,{primaryRegion:g}));for(let s=0;s<d.length;s++){const i=d[s],e=a[s];if(!(!i||!e))if(i.status==="fulfilled")if(i.value.success)p.push(`Account-${e.account.name}-${e.region}`),w.push({accountId:e.account.id,accountName:e.account.name,region:e.region,result:"succeeded",durationMs:i.value.duration});else{const C=O(i.value.error??"Unknown error");w.push({accountId:e.account.id,accountName:e.account.name,region:e.region,result:"failed",durationMs:i.value.duration,error:C}),n.push({accountId:e.account.id,error:C})}else{const C=O(M(i.reason));w.push({accountId:e.account.id,accountName:e.account.name,region:e.region,result:"failed",durationMs:0,error:C}),n.push({accountId:e.account.id,error:C})}}t.onCascadePhaseComplete?.("accounts")}if(u){t.onCascadePhaseStart?.("platform");const a=await $(o,r,c,u,"platform",u.region??g,t);if(a.success)p.push("Platform"),R={accountId:u.id,result:"succeeded",durationMs:a.duration};else{const d=O(a.error??"Platform destroy failed");n.push({accountId:u.id,error:d}),R={accountId:u.id,result:"failed",durationMs:a.duration,error:d}}t.onCascadePhaseComplete?.("platform")}const y={members:w,...R!==void 0?{platform:R}:{},domainsDeployed:!1,errors:n,totalDurationMs:Date.now()-A};if(t.onCascadeComplete?.(K(y)),t.onCascadeLedger?.(y),n.length>0){const a=n.map(s=>` ${s.accountId}: ${s.error}`).join(`
2
- `),d=new Error(`Cascade destroy completed with ${n.length} failure(s):
3
- ${a}`);t.onError?.(d),t.onLog?.(d.message,"warn")}}if(n.length>0)return t.onLog?.("Skipping organisation root stack destroy due to cascade failures","warn"),D({target:c.target,deploymentType:"organisation",stacksDestroyed:p,durationMs:Date.now()-S,warnings:n.map(y=>`${y.accountId}: ${y.error}`)});t.onStepStart?.(E,I,0,1);const P=await V(o,r,c,r.awsProvider.getRegion(),t);if(P.success)p.push("Organisation"),t.onStepComplete?.(E,I,"completed",0,1);else return t.onStepComplete?.(E,I,"error",0,1),N(P.error);return D({target:c.target,deploymentType:"organisation",stacksDestroyed:p,durationMs:Date.now()-S})}async function V(o,r,c,S,t){const l=x.buildDeploymentContext({deployType:"organisation",target:c.target,path:c.path,region:S,callerIdentity:b(r.awsProvider.getAccountId()),...B({orgConfig:o.orgConfig,identity:o.identity})},{verbose:o.options?.verbose},o.orgConfig),g=L(v.ORGANISATION);t.onLog?.("Synthesising organisation infrastructure\u2026","info");const m=await G(r,l,t,"Organisation synth failed");if(!m.success)return m;const u=await U(r.awsProvider.getClient(k),r.awsProvider.getClient(T),g,t,o.abortSignal),f=F(u);t.onLog?.(`Destroying ${g} stack\u2026`,"info");const h=await r.cdkService.runCdkDestroy(l,g,Y(t),j(t),r.awsProvider,!0);if(!h.success){const n=new Error(O(`Organisation destroy failed: ${h.error}`));return t.onError?.(n),N(n)}return f.length>0&&await H(r.awsProvider.getClient(T),f,t,o.abortSignal),D(void 0)}export{lt as destroyOrganisation};
@@ -1 +0,0 @@
1
- import{success as y,failure as h}from"@fjall/generator";import{OrganizationsClient as K}from"@aws-sdk/client-organizations";import{RAMClient as $}from"@aws-sdk/client-ram";import{CloudFormationClient as B}from"@aws-sdk/client-cloudformation";import{EC2Client as G}from"@aws-sdk/client-ec2";import{IAMClient as _}from"@aws-sdk/client-iam";import{BackupClient as z}from"@aws-sdk/client-backup";import{CostExplorerClient as H}from"@aws-sdk/client-cost-explorer";import{SSOAdminClient as q}from"@aws-sdk/client-sso-admin";import{ensureOrganisationExists as J}from"../aws/organisations/organisation.js";import{enablePolicyTypes as Q}from"../aws/organisations/policies.js";import{enableServiceAccess as V}from"../aws/organisations/serviceAccess.js";import{enableRamSharing as X}from"../aws/organisations/ram.js";import{activateTrustedAccess as Y}from"../aws/organisations/trustedAccess.js";import{enableIpamDelegatedAdmin as Z}from"../aws/organisations/ipam.js";import{updateBackupGlobalSettings as b}from"../aws/organisations/backup.js";import{listAccounts as w,createAccount as k}from"../aws/organisations/accounts.js";import{ensureOrganisationalUnitsExist as ee,placeAccountsInOUs as te,buildAccountToOUMap as ne}from"../aws/organisations/organisationalUnits.js";import{activateCostAllocationTags as oe}from"../aws/organisations/costAllocation.js";import{checkIdentityCentreStatus as re}from"../aws/organisations/identityCentre.js";import{registerSecurityDelegates as se}from"../aws/organisations/delegatedAdmin.js";import{enableCentralisedRootAccess as ie}from"../aws/organisations/rootAccess.js";import{selectImportedMemberAccounts as ae,formatImportedAccountsWarning as ce}from"../aws/organisations/importedAccounts.js";import{isOULeaf as ue}from"../aws/organisations/types.js";const Ke=["create-organisation","enable-policies","enable-service-access","enable-root-access","enable-ram-sharing","activate-trusted-access","enable-ipam","configure-backup","create-accounts","create-organisational-units","place-accounts","activate-cost-tags","check-identity-centre","register-security-delegates"];async function $e(n,r,e,o){const t=[],i=[],s=[],m=[];let a;const u=n.getClient(K),x=n.getClient(_),N=n.getClient($),S=n.getClient(B),L=n.getClient(G),W=n.getClient(z),F=n.getClient(H),j=n.getClient(q);e?.onPhaseStart?.("create-organisation"),e?.onProgress?.("Ensuring AWS Organisation exists");const f=await J(u,o);if(!f.success)return e?.onError?.("create-organisation",f.error),e?.onPhaseComplete?.("create-organisation","error"),h(f.error);const{orgId:D,rootId:I,managementAccountId:E}=f.data;if(e?.onPhaseComplete?.("create-organisation","completed"),t.push("create-organisation"),await d("enable-policies",()=>(e?.onProgress?.("Enabling organisation policy types"),Q(u,I,{abortSignal:o})),t,s,e),await d("enable-service-access",()=>(e?.onProgress?.("Enabling AWS service access"),V(u,o)),t,s,e),r.skipRootAccessManagement?p("enable-root-access",i,e):await d("enable-root-access",async()=>{e?.onProgress?.("Enabling centralised root access management");const c=await ie(x,o);return(c.success?c.data.enabled:c.error.partialSummary.enabled).length>0&&await me(u,E,e,o),c},t,s,e),await d("enable-ram-sharing",()=>(e?.onProgress?.("Enabling RAM sharing"),X(N,o)),t,s,e),await d("activate-trusted-access",()=>(e?.onProgress?.("Activating CloudFormation trusted access"),Y(S,o)),t,s,e),r.platformAccountId){const c=r.platformAccountId;await d("enable-ipam",()=>(e?.onProgress?.("Enabling IPAM delegated administrator"),Z(L,c,o)),t,s,e)}else p("enable-ipam",i,e);await d("configure-backup",()=>(e?.onProgress?.("Updating backup global settings"),b(W,void 0,o)),t,s,e),e?.onPhaseStart?.("create-accounts"),e?.onProgress?.("Checking for missing accounts");const P=await de(u,r.accounts,m,e,o);let R=[];P.success?(R=P.data,t.push("create-accounts"),e?.onPhaseComplete?.("create-accounts","completed")):C("create-accounts",P.error,s,e);let l={};e?.onPhaseStart?.("create-organisational-units"),e?.onProgress?.("Ensuring organisational units exist");const A=await ee(u,I,r.organisationalUnits,o);A.success?(l=A.data,t.push("create-organisational-units"),e?.onPhaseComplete?.("create-organisational-units","completed")):C("create-organisational-units",A.error,s,e);const g=Array.isArray(r.organisationalUnits)?void 0:r.organisationalUnits,O=r.accountPlacements??[],v=O.length===0&&g!==void 0?pe(g,R,E):O;if(Object.keys(l).length===0)p("place-accounts",i,e);else if(r.accountPlacements===void 0&&g===void 0){const c=new Error("Account placements not provided despite OUs being created. Caller must populate accountPlacements (flat-list mode cannot derive placements internally).");s.push({phase:"place-accounts",error:c.message}),e?.onPhaseStart?.("place-accounts"),e?.onError?.("place-accounts",c),e?.onPhaseComplete?.("place-accounts","error")}else if(v.length===0)p("place-accounts",i,e);else{const c=g?ne(g,l):void 0;await d("place-accounts",()=>(e?.onProgress?.("Placing accounts in organisational units"),te(u,l,v,c,o)),t,s,e)}const T=r.costAllocationTags??[];if(T.length>0?await d("activate-cost-tags",()=>(e?.onProgress?.("Activating cost allocation tags"),oe(F,T.map(c=>({TagKey:c})),o)),t,s,e):p("activate-cost-tags",i,e),r.skipIdentityCentre)p("check-identity-centre",i,e);else{e?.onPhaseStart?.("check-identity-centre"),e?.onProgress?.("Checking Identity Centre status");const c=await re(j,o);c.success?(a=c.data.enabled?"enabled":"not-enabled",t.push("check-identity-centre"),e?.onPhaseComplete?.("check-identity-centre","completed")):C("check-identity-centre",c.error,s,e)}const U=r.securityDelegateAccountId;return U?await d("register-security-delegates",()=>(e?.onProgress?.("Registering security service delegated administrators"),se(u,U,void 0,o)),t,s,e):p("register-security-delegates",i,e),y({organisationId:D,createdAccounts:m,identityCentreStatus:a,phasesCompleted:t,phasesSkipped:i,errors:s})}function C(n,r,e,o){e.push({phase:n,error:r.message}),o?.onError?.(n,r),o?.onPhaseComplete?.(n,"error")}function p(n,r,e){r.push(n),e?.onPhaseStart?.(n),e?.onPhaseComplete?.(n,"skipped")}async function me(n,r,e,o){const t=await w(n,o);if(!t.success){e?.onProgress?.(`Could not check for imported member accounts: ${t.error.message}`);return}const i=ae(t.data,r);if(i.length===0)return;const s=ce(i);e?.onWarning?e.onWarning("enable-root-access",s):e?.onProgress?.(s)}async function d(n,r,e,o,t){t?.onPhaseStart?.(n);const i=await r();i.success?(e.push(n),t?.onPhaseComplete?.(n,"completed")):C(n,i.error,o,t)}async function de(n,r,e,o,t){const i=await w(n,t);if(!i.success)return h(i.error);const s=new Set(i.data.map(a=>a.Name?.toLowerCase()).filter(a=>a!==void 0));for(const a of r){if(s.has(a.name.toLowerCase()))continue;o?.onProgress?.(`Account "${a.name}" is not yet a member of this AWS Organisation. Fjall will create a new account (with a new account ID). If "${a.name}" already exists as a standalone Fjall-connected account, abort now and import it into the organisation instead, because creating here produces a duplicate account. See the account-import runbook.`);const u=await k(n,a.name,a.email,void 0,void 0,t);if(!u.success)return h(u.error);e.push({name:u.data.accountName,accountId:u.data.accountId})}if(e.length===0)return y(i.data);const m=await w(n,t);return m.success?y(m.data):h(m.error)}function M(n){const r=[];for(const[e,o]of Object.entries(n))if(ue(o))for(const t of o)r.push({accountName:t,placementKey:e});else r.push(...M(o));return r}function pe(n,r,e){const o=M(n),t=new Map;for(const s of r){const m=s.Name?.toLowerCase();m&&s.Id&&t.set(m,s)}const i=[];for(const{accountName:s,placementKey:m}of o){const a=t.get(s.toLowerCase());!a?.Id||!a.Name||e!==void 0&&a.Id===e||i.push({id:a.Id,name:a.Name,environment:m})}return i}export{Ke as ORG_SETUP_PHASES,$e as runOrganisationSetup};
@@ -1 +0,0 @@
1
- import{z as u}from"zod";import{success as T,failure as i}from"@fjall/generator";import{environmentToTier as v,stageFromWireEnvironment as y,normaliseError as g}from"@fjall/util";import{OrganizationsClient as E}from"@aws-sdk/client-organizations";import{listAccounts as P}from"../aws/organisations/accounts.js";import{parseAccountsConfiguration as S,flattenAccountsToEnvironments as h}from"./accountsConfig.js";const w=6e4,O=u.object({Id:u.string().min(1),Name:u.string().min(1)});async function b(s,c){let n;const r=new Promise((o,e)=>{n=setTimeout(()=>e(new Error(`Timed out parsing ACCOUNTS configuration after ${w}ms`)),w)});let t;try{t=await Promise.race([S(c),r])}catch(o){return i(g(o))}finally{n!==void 0&&clearTimeout(n)}if(!t.success)return i(new Error(`Failed to parse ACCOUNTS configuration: ${t.error.message}`));if(t.data===null)return i(new Error("ACCOUNTS configuration file not found"));const C=h(t.data),f=new Map;for(const{accountName:o,environment:e}of C)f.set(o.toLowerCase(),{environment:e,displayName:o});let d;try{d=s.awsProvider.getClient(E)}catch(o){return i(g(o))}const a=await P(d);if(!a.success)return i(a.error);const l=new Map;for(const o of a.data){const e=O.safeParse(o);e.success&&l.set(e.data.Name.toLowerCase(),{id:e.data.Id,name:e.data.Name})}const p=[],A=[];for(const[o,{environment:e,displayName:N}]of f){const m=l.get(o);if(!m){A.push(N);continue}v(e)!=="organisation"&&p.push({id:m.id,name:m.name,tier:v(e),environment:y(e)})}return T({providerAccounts:p,missingAccountNames:A})}function z(s,c){const n=new Map;for(const r of s?.providerAccounts??[])n.set(r.id,r);for(const r of c)n.has(r.id)||n.set(r.id,r);return{...s??{},providerAccounts:Array.from(n.values())}}export{z as mergeReconciledProviderAccounts,b as reconcileProviderAccounts};
@@ -1 +0,0 @@
1
- import{success as a,failure as f}from"@fjall/generator";import{maskSensitiveOutput as s}from"@fjall/util";import{STEP_IDS as i}from"../types/stepDefinitions.js";const p="Tagging container images";async function R(E,m,r,e){const n=E.dockerProvider;if(!n)return a(void 0);const g=m.awsProvider.getAccountId();if(!g)return e.onLog?.("Skipping ECR initialisation \u2014 account ID not available","warn"),a(void 0);const d=m.awsProvider.getRegion();e.onStepStart?.(i.DOCKER_OPERATIONS,"Initialising container repository"),e.onLog?.("Initialising ECR repository with welcome image\u2026","info");const S=await n.initialiseECR({appName:r.appName,region:d,accountId:g});if(S.success||e.onLog?.(s(`ECR initialisation warning: ${S.error.message}`),"warn"),e.onStepComplete?.(i.DOCKER_OPERATIONS,"Initialising container repository","completed"),n.tagImages){e.onStepStart?.(i.TAG_ECR_IMAGES,p),e.onLog?.("Tagging ECR images for ECS services\u2026","info");const u=o=>{e.onLog?.(s(o),"info")},t=await n.tagImages({appName:r.appName,appPath:r.path,region:d,accountId:g},u);if(!t.success){const o=new Error(s(t.error.message));return e.onError?.(o),e.onStepComplete?.(i.TAG_ECR_IMAGES,p,"error"),f(o)}e.onLog?.(`Tagged ${t.data.taggedServices.length} service(s): ${t.data.taggedServices.join(", ")}`,"info");for(const o of t.data.taggedServices)e.onStepStart?.(`tag-ecr-images-${o}`,`Tagged ${o}`),e.onStepComplete?.(`tag-ecr-images-${o}`,`Tagged ${o}`,"completed");e.onStepComplete?.(i.TAG_ECR_IMAGES,p,"completed")}return a(void 0)}export{R as runWelcomeImageSetup};
@@ -1 +0,0 @@
1
- import{existsSync as s,readFileSync as a}from"fs";import{join as i}from"path";import{logger as f}from"@fjall/util/logger";import{getErrorMessage as m}from"@fjall/util";import{APPLICATION_STACKS as e}from"./operations.js";import{INFRASTRUCTURE_FILENAME as u}from"./constants.js";import{OPENNEXT_PATTERNS as g,isOpenNextPattern as C}from"./patternTypes.js";function p(t){const r=o=>t.some(n=>n.endsWith(o));return{hasNetwork:r(e.NETWORK),hasCompute:r(e.COMPUTE),hasDatabase:r(e.DATABASE),hasStorage:r(e.STORAGE),hasMessaging:r(e.MESSAGING),hasCdn:r(e.CDN)}}function h(t){try{const r=i(t,u);return s(r)?a(r,"utf-8"):null}catch(r){return f.debug("patternDetection","Failed to read infrastructure file",{appPath:t,error:m(r)}),null}}export{g as OPENNEXT_PATTERNS,p as deriveResourcesFromManifestStacks,C as isOpenNextPattern,h as readInfrastructureContent};