@fjall/deploy-core 2.19.2 → 2.19.4

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
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 152 files minified at 2026-06-20T09:23:13.824Z
1
+ 152 files minified at 2026-06-22T21:30:21.946Z
@@ -1,6 +1,6 @@
1
1
  import { type IAMClient } from "@aws-sdk/client-iam";
2
2
  import { type Result } from "@fjall/generator";
3
- import type { OrgConfig } from "../../types/orgConfig.js";
3
+ import type { OrgConfig } from "../../types/config/orgConfig.js";
4
4
  export declare const ACCOUNT_GLOBALS_ROLE_NAME = "FjallMonitoring";
5
5
  /**
6
6
  * Probe whether the fixed-name account globals exist in this account — the
@@ -1,7 +1,7 @@
1
1
  import { type CloudFormationClient } from "@aws-sdk/client-cloudformation";
2
2
  import { type EC2Client } from "@aws-sdk/client-ec2";
3
3
  import { type Result } from "@fjall/generator";
4
- import type { OrgConfig } from "../types/orgConfig.js";
4
+ import type { OrgConfig } from "../types/config/orgConfig.js";
5
5
  export declare const ACCOUNT_STACK_NAME: string;
6
6
  export type TargetReadinessReason = {
7
7
  kind: "missing-account-stack";
@@ -1,4 +1,4 @@
1
- import type { OrgConfig } from "../types/orgConfig.js";
1
+ import type { OrgConfig } from "../types/config/orgConfig.js";
2
2
  import { type TargetReadinessReason } from "./targetReadiness.js";
3
3
  /**
4
4
  * Coupled to buildTargetUnreadyAdvisory at ./targetReadiness.ts — both open
@@ -1,7 +1,7 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployParams, DeployResult } from "../types/params.js";
3
- import type { ApplicationOperation } from "../types/operations.js";
4
- import type { DeployServices } from "./serviceFactory.js";
2
+ import type { DeployParams, DeployResult } from "../../types/params.js";
3
+ import type { ApplicationOperation } from "../../types/operations.js";
4
+ import type { DeployServices } from "../serviceFactory.js";
5
5
  /**
6
6
  * Core application deployment orchestration.
7
7
  *
@@ -0,0 +1 @@
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"../organisation/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,11 +1,11 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployParams } from "../types/params.js";
3
- import type { DeploymentContext } from "../types/deployment/DeploymentTypes.js";
4
- import type { DeployCallbacks } from "../types/callbacks.js";
5
- import type { ApplicationOperation, ApplicationStack } from "../types/operations.js";
6
- import type { DeployServices } from "./serviceFactory.js";
2
+ import type { DeployParams } from "../../types/params.js";
3
+ import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
4
+ import type { DeployCallbacks } from "../../types/callbacks.js";
5
+ import type { ApplicationOperation, ApplicationStack } from "../../types/operations.js";
6
+ import type { DeployServices } from "../serviceFactory.js";
7
7
  import type { DetectionResult } from "./detectionPipeline.js";
8
- import type { BuildCallbacks } from "../types/frameworkBuilder.js";
8
+ import type { BuildCallbacks } from "../../types/frameworkBuilder.js";
9
9
  /**
10
10
  * Identify Phase 2 stacks (Storage, Messaging, Database) that are consecutive
11
11
  * in the deploy order and have changes. Returns them for parallel deployment
@@ -1,4 +1,4 @@
1
- import{success as h,failure as S}from"@fjall/generator";import{maskSensitiveOutput as w}from"@fjall/util";import{APPLICATION_STACKS as R,getApplicationStackName as C,getApplicationStepName as y,getApplicationStepId as N,PARALLEL_DEPLOY_GROUPS as A}from"../types/operations.js";import{emitProgress as E}from"../services/supporting/helpers.js";import{forwardOutput as O,forwardResourceProgress as L}from"./contextHelpers.js";import{runDockerBuild as I}from"./dockerBuildHelper.js";import{runWelcomeImageSetup as _}from"./welcomeImageHelper.js";import{withStepLifecycle as k}from"./stepLifecycle.js";function F(r,n,i,p,t){const l=new Set(A.PHASE_2.stacks),u=[];for(let o=n;o<r.length&&l.has(r[o]);o++)u.push(r[o]);if(u.length<2)return[];if(!t){const o=u.filter(f=>{const m=C(p,f);return i.get(m)!==!1});return o.length>=2?o:[]}return u}async function G(r,n,i,p,t,l,u,o,f,m){for(let e=0;e<r.length;e++){const s=r[e],c=N(s,"deploy"),d=y(s,"deploy");t.onStepStart?.(c,d,l+e,u)}E(t,"Deploying infrastructure in parallel\u2026"),t.onParallelPhaseStart?.(r,"Storage and database resources (parallel)");const a=await i.stackService.deployStacksInParallel(r,p,{onOutput:O(t),onResourceProgress:(e,s)=>{t.onResourceProgress?.(e),s&&t.onParallelStackResourceProgress?.(s,e)},onStackComplete:(e,s,c,d)=>{const P=N(e,"deploy"),B=y(e,"deploy"),x=l+r.indexOf(e);t.onStepComplete?.(P,B,s?"completed":"error",x,u),!s&&d&&t.onError?.(new Error(w(d.message)))}});if(!a.success)return t.onParallelPhaseComplete?.([]),S(a.error);const g=a.data.filter(e=>!e.success);if(t.onParallelPhaseComplete?.(a.data.map(e=>({stack:e.stack,success:e.success,error:e.error}))),g.length>0){const e=g.map(c=>c.stack).join(", "),s=g.map(c=>`${c.stack}: ${w(c.error?.message??"Unknown error")}`).join(`
1
+ import{success as h,failure as S}from"@fjall/generator";import{maskSensitiveOutput as w}from"@fjall/util";import{APPLICATION_STACKS as R,getApplicationStackName as C,getApplicationStepName as y,getApplicationStepId as N,PARALLEL_DEPLOY_GROUPS as A}from"../../types/operations.js";import{emitProgress as E}from"../../services/supporting/helpers.js";import{forwardOutput as O,forwardResourceProgress as L}from"../contextHelpers.js";import{runDockerBuild as I}from"./dockerBuildHelper.js";import{runWelcomeImageSetup as _}from"./welcomeImageHelper.js";import{withStepLifecycle as k}from"../stepLifecycle.js";function F(r,n,i,p,t){const l=new Set(A.PHASE_2.stacks),u=[];for(let o=n;o<r.length&&l.has(r[o]);o++)u.push(r[o]);if(u.length<2)return[];if(!t){const o=u.filter(f=>{const m=C(p,f);return i.get(m)!==!1});return o.length>=2?o:[]}return u}async function G(r,n,i,p,t,l,u,o,f,m){for(let e=0;e<r.length;e++){const s=r[e],c=N(s,"deploy"),d=y(s,"deploy");t.onStepStart?.(c,d,l+e,u)}E(t,"Deploying infrastructure in parallel\u2026"),t.onParallelPhaseStart?.(r,"Storage and database resources (parallel)");const a=await i.stackService.deployStacksInParallel(r,p,{onOutput:O(t),onResourceProgress:(e,s)=>{t.onResourceProgress?.(e),s&&t.onParallelStackResourceProgress?.(s,e)},onStackComplete:(e,s,c,d)=>{const P=N(e,"deploy"),B=y(e,"deploy"),x=l+r.indexOf(e);t.onStepComplete?.(P,B,s?"completed":"error",x,u),!s&&d&&t.onError?.(new Error(w(d.message)))}});if(!a.success)return t.onParallelPhaseComplete?.([]),S(a.error);const g=a.data.filter(e=>!e.success);if(t.onParallelPhaseComplete?.(a.data.map(e=>({stack:e.stack,success:e.success,error:e.error}))),g.length>0){const e=g.map(c=>c.stack).join(", "),s=g.map(c=>`${c.stack}: ${w(c.error?.message??"Unknown error")}`).join(`
2
2
  `);return S(new Error(`Failed to deploy stacks: ${e}
3
3
 
4
4
  ${s}`))}for(const e of a.data){if(e.success&&e.outputs)for(const[d,P]of Object.entries(e.outputs))f[d]=String(P);const s=C(n.appName,e.stack),c=o.currentHashes.get(s);c&&m.set(s,c)}return h(void 0)}async function K(r,n,i,p,t,l,u,o){const f=N(r,"deploy"),m=y(r,"deploy");return k(p,{stepId:f,stepName:m,stepIndex:t,totalSteps:l},async()=>{const a=await n.stackService.deployStack(r,i,{onOutput:O(p),onResourceProgress:L(p)},o?.parameters!==void 0?{parameters:o.parameters}:void 0);if(!a.success)return{kind:"error",error:a.error};if(a.data.outputs)for(const[g,e]of Object.entries(a.data.outputs))u[g]=String(e);return{kind:"completed",data:void 0}})}async function M(r,n,i,p,t,l){if(r!==R.COMPUTE||!n.dockerProvider)return null;if(l){const o=await I(n,i,p,t);return o.success?h({contentHashTagsByService:o.data}):S(o.error)}const u=await _(n,i,p,t);return u.success?h({contentHashTagsByService:{}}):S(u.error)}function W(r){return{onBuildStart:n=>{r.onOpenNextBuildStart?.(),r.onLog?.(`${n} build started`,"info")},onBuildProgress:(n,i)=>{r.onOpenNextProgress?.(i)},onBuildComplete:n=>{r.onOpenNextBuildComplete?.(),r.onLog?.(`${n} build complete`,"info")},onBuildError:(n,i)=>{r.onOpenNextBuildError?.(i)}}}export{W as createBuildCallbacks,G as deployParallelPhase,K as deployStackSequential,F as getParallelPhase2Stacks,M as runDockerPreCompute};
@@ -1,7 +1,7 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DestroyParams, DestroyResult } from "../types/params.js";
3
- import type { ApplicationOperation } from "../types/operations.js";
4
- import type { DeployServices } from "./serviceFactory.js";
2
+ import type { DestroyParams, DestroyResult } from "../../types/params.js";
3
+ import type { ApplicationOperation } from "../../types/operations.js";
4
+ import type { DeployServices } from "../serviceFactory.js";
5
5
  /**
6
6
  * Core application destruction orchestration.
7
7
  *
@@ -0,0 +1 @@
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/detection/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/config/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,7 +1,7 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployParams, DeployResult } from "../types/params.js";
3
- import type { ApplicationOperation } from "../types/operations.js";
4
- import type { DeployServices } from "./serviceFactory.js";
2
+ import type { DeployParams, DeployResult } from "../../types/params.js";
3
+ import type { ApplicationOperation } from "../../types/operations.js";
4
+ import type { DeployServices } from "../serviceFactory.js";
5
5
  /**
6
6
  * Re-base an operator-supplied `--image-tag` onto a specific ECS service.
7
7
  *
@@ -0,0 +1 @@
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,9 +1,9 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployCallbacks } from "../types/callbacks.js";
3
- import type { DeploymentContext } from "../types/deployment/DeploymentTypes.js";
4
- import type { ApplicationOperation } from "../types/operations.js";
5
- import type { DeployServices } from "./serviceFactory.js";
6
- import type { DetectionResult } from "../types/detection.js";
2
+ import type { DeployCallbacks } from "../../types/callbacks.js";
3
+ import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
4
+ import type { ApplicationOperation } from "../../types/operations.js";
5
+ import type { DeployServices } from "../serviceFactory.js";
6
+ import type { DetectionResult } from "../../types/detection/detection.js";
7
7
  export type { DetectionResult };
8
8
  /**
9
9
  * Pre-deployment analysis pipeline.
@@ -0,0 +1 @@
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/detection/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,8 +1,8 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployParams } from "../types/params.js";
3
- import type { DeployCallbacks } from "../types/callbacks.js";
4
- import type { ApplicationOperation } from "../types/operations.js";
5
- import type { DeployServices } from "./serviceFactory.js";
2
+ import type { DeployParams } from "../../types/params.js";
3
+ import type { DeployCallbacks } from "../../types/callbacks.js";
4
+ import type { ApplicationOperation } from "../../types/operations.js";
5
+ import type { DeployServices } from "../serviceFactory.js";
6
6
  export declare const DOCKER_BUILD_STEP_NAME = "Building and pushing Docker image";
7
7
  /**
8
8
  * Run Docker build and push before deploying the Compute stack.
@@ -0,0 +1 @@
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};
@@ -8,9 +8,9 @@
8
8
  * For Payload apps, generates the import map before building.
9
9
  */
10
10
  import { type Result } from "@fjall/generator";
11
- import type { DeployCallbacks } from "../types/callbacks.js";
12
- import type { DeployOptions } from "../types/params.js";
13
- import type { ApplicationOperation } from "../types/operations.js";
11
+ import type { DeployCallbacks } from "../../types/callbacks.js";
12
+ import type { DeployOptions } from "../../types/params.js";
13
+ import type { ApplicationOperation } from "../../types/operations.js";
14
14
  /** Default build timeout: 10 minutes */
15
15
  export declare const BUILD_TIMEOUT_MS = 600000;
16
16
  /** Timeout for Payload import map generation: 30 seconds */
@@ -1,3 +1,3 @@
1
- import{createRequire as _}from"module";import{existsSync as g,statSync as S,rmSync as $}from"fs";import{join as l}from"path";import{success as f,failure as i}from"@fjall/generator";import{filterDangerousEnvVars as y,maskSensitiveOutput as d,parseShellArgs as v}from"@fjall/util";import{logger as c}from"@fjall/util/logger";import{isOpenNextPattern as b}from"../types/patternDetection.js";import{spawnWithTimeout as E}from"./spawnHelpers.js";const x="@opennextjs/aws",P=6e5,w=3e4;function T(s){const n=_(l(s,"package.json"));let e;try{e=n.resolve.paths(x)}catch(r){return i(new Error(`Cannot determine module search paths for ${x}. Ensure it is installed in your project. (${r instanceof Error?r.message:String(r)})`))}if(!e||e.length===0)return i(new Error(`Cannot determine module search paths for ${x}. Ensure it is installed in your project.`));for(const r of e){const t=l(r,"@opennextjs","aws");try{if(S(t).isDirectory()){const p=l(r,".bin","open-next");if(!g(p)){c.debug("openNextBuild","Package found but binary missing",{nodeModulesPath:r,binPath:p});continue}return c.debug("openNextBuild","Resolved OpenNext binary",{binPath:p}),f(p)}}catch(o){c.debug("openNextBuild","Package not at location",{nodeModulesPath:r,error:String(o)})}}return i(new Error(`Cannot find ${x} package. Ensure it is installed in your project: npm install ${x}`))}function O(s){const n=l(s,".open-next");if(g(n))try{$(n,{recursive:!0,force:!0}),c.debug("openNextBuild","Cleaned up stale .open-next directory",{path:n})}catch(e){c.debug("openNextBuild","Failed to clean up .open-next",{error:String(e)})}}function B(s,n,e,r){switch(s.type){case"timeout":return e?.(),r?.(n.timeoutMsg),i(new Error(n.timeoutMsg));case"enoent":return e?.(),r?.(n.enoentMsg),i(new Error(n.enoentMsg));case"spawn_error":{const t=`${n.spawnErrorPrefix}: ${s.error.message}`;return e?.(),r?.(t),i(new Error(t))}default:return}}async function D(s,n){n.onOpenNextProgress?.(d("Generating Payload import map..."));const[e,...r]=v("npx payload generate:importmap"),t=y(globalThis.process.env),o=await E({command:e,args:r,cwd:s,env:{...t,FORCE_COLOR:"0"},timeout:w,onStdoutData:m=>{n.onOpenNextProgress?.(d(m.trim()))}}),p=B(o,{timeoutMsg:`Payload import map generation timed out after ${w/1e3} seconds`,enoentMsg:"Payload CLI not found. Ensure payload is installed: npm install payload",spawnErrorPrefix:"Failed to generate Payload import map"});if(p)return p;if(o.type!=="success")return i(new Error("Unexpected spawn result"));if(o.code!==0){const m=d(o.stderr||o.stdout);return i(new Error(`Payload import map generation failed (exit code ${o.code}):
1
+ import{createRequire as _}from"module";import{existsSync as g,statSync as S,rmSync as $}from"fs";import{join as l}from"path";import{success as f,failure as i}from"@fjall/generator";import{filterDangerousEnvVars as y,maskSensitiveOutput as d,parseShellArgs as v}from"@fjall/util";import{logger as c}from"@fjall/util/logger";import{isOpenNextPattern as b}from"../../types/detection/patternDetection.js";import{spawnWithTimeout as E}from"../spawnHelpers.js";const x="@opennextjs/aws",P=6e5,w=3e4;function T(s){const n=_(l(s,"package.json"));let e;try{e=n.resolve.paths(x)}catch(r){return i(new Error(`Cannot determine module search paths for ${x}. Ensure it is installed in your project. (${r instanceof Error?r.message:String(r)})`))}if(!e||e.length===0)return i(new Error(`Cannot determine module search paths for ${x}. Ensure it is installed in your project.`));for(const r of e){const t=l(r,"@opennextjs","aws");try{if(S(t).isDirectory()){const p=l(r,".bin","open-next");if(!g(p)){c.debug("openNextBuild","Package found but binary missing",{nodeModulesPath:r,binPath:p});continue}return c.debug("openNextBuild","Resolved OpenNext binary",{binPath:p}),f(p)}}catch(o){c.debug("openNextBuild","Package not at location",{nodeModulesPath:r,error:String(o)})}}return i(new Error(`Cannot find ${x} package. Ensure it is installed in your project: npm install ${x}`))}function O(s){const n=l(s,".open-next");if(g(n))try{$(n,{recursive:!0,force:!0}),c.debug("openNextBuild","Cleaned up stale .open-next directory",{path:n})}catch(e){c.debug("openNextBuild","Failed to clean up .open-next",{error:String(e)})}}function B(s,n,e,r){switch(s.type){case"timeout":return e?.(),r?.(n.timeoutMsg),i(new Error(n.timeoutMsg));case"enoent":return e?.(),r?.(n.enoentMsg),i(new Error(n.enoentMsg));case"spawn_error":{const t=`${n.spawnErrorPrefix}: ${s.error.message}`;return e?.(),r?.(t),i(new Error(t))}default:return}}async function D(s,n){n.onOpenNextProgress?.(d("Generating Payload import map..."));const[e,...r]=v("npx payload generate:importmap"),t=y(globalThis.process.env),o=await E({command:e,args:r,cwd:s,env:{...t,FORCE_COLOR:"0"},timeout:w,onStdoutData:m=>{n.onOpenNextProgress?.(d(m.trim()))}}),p=B(o,{timeoutMsg:`Payload import map generation timed out after ${w/1e3} seconds`,enoentMsg:"Payload CLI not found. Ensure payload is installed: npm install payload",spawnErrorPrefix:"Failed to generate Payload import map"});if(p)return p;if(o.type!=="success")return i(new Error("Unexpected spawn result"));if(o.code!==0){const m=d(o.stderr||o.stdout);return i(new Error(`Payload import map generation failed (exit code ${o.code}):
2
2
  ${m}`))}return c.debug("openNextBuild","Payload import map generated",{appPath:s}),n.onOpenNextProgress?.(d("Payload import map generated")),f(void 0)}async function H(s,n,e,r){if(!b(n))return f(void 0);if(r?.skipBuild||r?.infraOnly)return c.debug("openNextBuild","Build skipped",{skipBuild:r?.skipBuild,infraOnly:r?.infraOnly}),e.onLog?.(r?.infraOnly?"Infrastructure-only mode \u2014 skipping OpenNext build":"Build skipped (--skip-build)","info"),f(void 0);const t=s.path;if(n==="payload"){const u=await D(t,e);if(!u.success)return e.onOpenNextBuildError?.(d(u.error.message)),O(t),i(u.error)}const o=T(t);if(!o.success)return e.onOpenNextBuildError?.(d(o.error.message)),i(o.error);const p=o.data;e.onOpenNextBuildStart?.(),e.onOpenNextProgress?.(d("Starting OpenNext build..."));const m=y(globalThis.process.env),M=l(t,"node_modules"),a=await E({command:p,args:["build"],cwd:t,env:{...m,FORCE_COLOR:"0",NODE_PATH:M,npm_config_loglevel:"error",PNPM_HOME:m.PNPM_HOME||""},timeout:P,onStdoutData:u=>{e.onOpenNextProgress?.(d(u.trim()))},onStderrData:u=>{e.onOpenNextProgress?.(d(u.trim()))}}),N=B(a,{timeoutMsg:`OpenNext build timed out after ${P/1e3} seconds`,enoentMsg:"OpenNext binary not found. Ensure Node.js is installed and in PATH.",spawnErrorPrefix:"Failed to spawn OpenNext build"},()=>O(t),u=>e.onOpenNextBuildError?.(u));if(N)return N;if(a.type!=="success")return i(new Error("Unexpected spawn result"));if(a.code!==0){O(t);const u=d(a.stderr||a.stdout);return e.onOpenNextBuildError?.(`OpenNext build failed (exit code ${a.code})`),i(new Error(`OpenNext build failed (exit code ${a.code}):
3
3
  ${u}`))}const h=l(t,".open-next");return g(h)?(e.onOpenNextBuildComplete?.(),e.onOpenNextProgress?.(d("OpenNext build completed successfully")),f(void 0)):(e.onOpenNextBuildError?.("OpenNext build completed but .open-next directory not found"),i(new Error("OpenNext build completed but .open-next directory not found")))}export{P as BUILD_TIMEOUT_MS,w as IMPORT_MAP_TIMEOUT_MS,H as runOpenNextBuild};
@@ -1,8 +1,8 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployParams } from "../types/params.js";
3
- import type { DeployCallbacks } from "../types/callbacks.js";
4
- import type { ApplicationOperation } from "../types/operations.js";
5
- import type { DeployServices } from "./serviceFactory.js";
2
+ import type { DeployParams } from "../../types/params.js";
3
+ import type { DeployCallbacks } from "../../types/callbacks.js";
4
+ import type { ApplicationOperation } from "../../types/operations.js";
5
+ import type { DeployServices } from "../serviceFactory.js";
6
6
  /**
7
7
  * Initialise ECR with the welcome image and tag for ECS services.
8
8
  * Runs before Compute stack for apps without a Dockerfile.
@@ -0,0 +1 @@
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 +1 @@
1
- import{basename as i}from"path";import{success as p}from"@fjall/generator";import{logger as d}from"@fjall/util/logger";import{OPENNEXT_DEPLOY_ORDER as l,OPENNEXT_DESTROY_ORDER as c}from"../../types/operations.js";import{readInfrastructureContent as m}from"../../types/patternDetection.js";import{runOpenNextBuild as f,BUILD_TIMEOUT_MS as y,IMPORT_MAP_TIMEOUT_MS as x}from"../openNextBuild.js";function o(r){return{hasNetwork:!0,hasCompute:!0,hasDatabase:r,hasStorage:!0,hasMessaging:!0,hasCdn:!0}}const E={name:"opennext",priority:10,detect(r){const t={detected:!1,pattern:null,reason:"No OpenNext pattern found",resources:o(!1),hasDatabase:!1,hasDockerfile:!1},e=m(r.appPath);if(!e)return{...t,reason:"No infrastructure.ts found"};if(!e.includes("PatternFactory"))return{...t,reason:"No PatternFactory reference in infrastructure.ts"};if(e.includes('type: "payload"'))return{detected:!0,pattern:"payload",reason:'PatternFactory with type: "payload" detected',resources:o(!0),hasDatabase:!0,hasDockerfile:!1};if(e.includes('type: "nextjs"')){const n=e.includes("DatabaseFactory.build(")||e.includes("database:");return{detected:!0,pattern:"nextjs",reason:'PatternFactory with type: "nextjs" detected',resources:o(n),hasDatabase:n,hasDockerfile:!1}}return{...t,reason:"PatternFactory found but no recognised pattern type"}},plan(r,t){const e=t.pattern==="payload"?[{name:"payload-import-map",binary:"npx",args:["payload","generate:importmap"],timeoutMs:x}]:[];return{builderName:"opennext",deployOrder:l,destroyOrder:c,parallelDestroy:!0,preBuildCommands:e,buildCommand:{name:"open-next-build",binary:"open-next",args:["build"],timeoutMs:y},requiresDockerBuild:!1,resources:t.resources}},async build(r,t,e,n){if(n?.skipBuild||n?.infraOnly)return d.debug("openNextBuilder","Build skipped",{skipBuild:n.skipBuild,infraOnly:n.infraOnly}),p(void 0);const s={kind:"application",appName:i(r)||"app",path:r},u=t.preBuildCommands.some(a=>a.name==="payload-import-map")?"payload":"nextjs";return await f(s,u,{onOpenNextBuildStart:()=>e.onBuildStart?.("opennext"),onOpenNextProgress:a=>e.onBuildProgress?.("opennext",a),onOpenNextBuildComplete:()=>e.onBuildComplete?.("opennext"),onOpenNextBuildError:a=>e.onBuildError?.("opennext",a)})}};export{E as openNextBuilder};
1
+ import{basename as i}from"path";import{success as p}from"@fjall/generator";import{logger as d}from"@fjall/util/logger";import{OPENNEXT_DEPLOY_ORDER as l,OPENNEXT_DESTROY_ORDER as c}from"../../types/operations.js";import{readInfrastructureContent as m}from"../../types/detection/patternDetection.js";import{runOpenNextBuild as f,BUILD_TIMEOUT_MS as y,IMPORT_MAP_TIMEOUT_MS as x}from"../application/openNextBuild.js";function o(r){return{hasNetwork:!0,hasCompute:!0,hasDatabase:r,hasStorage:!0,hasMessaging:!0,hasCdn:!0}}const E={name:"opennext",priority:10,detect(r){const t={detected:!1,pattern:null,reason:"No OpenNext pattern found",resources:o(!1),hasDatabase:!1,hasDockerfile:!1},e=m(r.appPath);if(!e)return{...t,reason:"No infrastructure.ts found"};if(!e.includes("PatternFactory"))return{...t,reason:"No PatternFactory reference in infrastructure.ts"};if(e.includes('type: "payload"'))return{detected:!0,pattern:"payload",reason:'PatternFactory with type: "payload" detected',resources:o(!0),hasDatabase:!0,hasDockerfile:!1};if(e.includes('type: "nextjs"')){const n=e.includes("DatabaseFactory.build(")||e.includes("database:");return{detected:!0,pattern:"nextjs",reason:'PatternFactory with type: "nextjs" detected',resources:o(n),hasDatabase:n,hasDockerfile:!1}}return{...t,reason:"PatternFactory found but no recognised pattern type"}},plan(r,t){const e=t.pattern==="payload"?[{name:"payload-import-map",binary:"npx",args:["payload","generate:importmap"],timeoutMs:x}]:[];return{builderName:"opennext",deployOrder:l,destroyOrder:c,parallelDestroy:!0,preBuildCommands:e,buildCommand:{name:"open-next-build",binary:"open-next",args:["build"],timeoutMs:y},requiresDockerBuild:!1,resources:t.resources}},async build(r,t,e,n){if(n?.skipBuild||n?.infraOnly)return d.debug("openNextBuilder","Build skipped",{skipBuild:n.skipBuild,infraOnly:n.infraOnly}),p(void 0);const s={kind:"application",appName:i(r)||"app",path:r},u=t.preBuildCommands.some(a=>a.name==="payload-import-map")?"payload":"nextjs";return await f(s,u,{onOpenNextBuildStart:()=>e.onBuildStart?.("opennext"),onOpenNextProgress:a=>e.onBuildProgress?.("opennext",a),onOpenNextBuildComplete:()=>e.onBuildComplete?.("opennext"),onOpenNextBuildError:a=>e.onBuildError?.("opennext",a)})}};export{E as openNextBuilder};
@@ -1,7 +1,7 @@
1
1
  import { type Result } from "@fjall/generator";
2
2
  import type { TrailLifecycleState } from "@fjall/util/config";
3
3
  import type { ResourceEvent } from "@fjall/util/aws";
4
- import type { OrgConfig } from "../types/orgConfig.js";
4
+ import type { OrgConfig } from "../types/config/orgConfig.js";
5
5
  import type { DeployIdentity } from "../types/credentials.js";
6
6
  import type { AwsProviderCredentials } from "../aws/AwsProvider.js";
7
7
  import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
@@ -1 +1 @@
1
- import{success as a}from"@fjall/generator";import{isApplicationOperation as c}from"../types/operations.js";import{createDeployServices as u}from"./serviceFactory.js";import{resolveOperation as d}from"./resolveOperation.js";import{deployApplication as p}from"./applicationDeploy.js";import{deployOrganisation as f}from"./organisationDeploy.js";async function g(t){const n=Date.now(),s=u(t),o=await d(t.target,t.workingDirectory);if(!o.success)return t.callbacks.onError?.(o.error),o;const i=o.data;if(c(i)){const e=await p(t,s,i);return e.success&&e.data.durationMs===void 0?a({...e.data,durationMs:Date.now()-n}):e}const r=await f(t,s,i);return r.success&&r.data.durationMs===void 0?a({...r.data,durationMs:Date.now()-n}):r}export{g as deploy};
1
+ import{success as a}from"@fjall/generator";import{isApplicationOperation as c}from"../types/operations.js";import{createDeployServices as u}from"./serviceFactory.js";import{resolveOperation as d}from"./resolveOperation.js";import{deployApplication as p}from"./application/applicationDeploy.js";import{deployOrganisation as f}from"./organisation/organisationDeploy.js";async function g(t){const n=Date.now(),s=u(t),o=await d(t.target,t.workingDirectory);if(!o.success)return t.callbacks.onError?.(o.error),o;const i=o.data;if(c(i)){const e=await p(t,s,i);return e.success&&e.data.durationMs===void 0?a({...e.data,durationMs:Date.now()-n}):e}const r=await f(t,s,i);return r.success&&r.data.durationMs===void 0?a({...r.data,durationMs:Date.now()-n}):r}export{g as deploy};
@@ -1 +1 @@
1
- import{success as u,failure as s}from"@fjall/generator";import{isApplicationOperation as a,isOrganisationOperation as d,getApplicationStackName as g,getOrganisationStackName as p,APPLICATION_STACKS as k}from"../types/operations.js";import{createDeployServices as y}from"./serviceFactory.js";import{resolveOperation as w}from"./resolveOperation.js";import{destroyApplication as b}from"./applicationDestroy.js";import{destroyOrganisation as v}from"./organisationDestroy.js";import{checkActiveDeployments as C}from"./activeDeploymentGuard.js";async function T(o){const f=Date.now(),i=y({target:o.target,workingDirectory:o.workingDirectory,awsCredentials:o.awsCredentials,callbacks:o.callbacks,options:{verbose:o.options?.verbose,force:o.options?.force,cascade:o.options?.cascade,skipConfirmation:o.options?.skipConfirmation},orgConfig:o.orgConfig,identity:o.identity}),r=await w(o.target,o.workingDirectory);if(!r.success)return o.callbacks.onError?.(r.error),r;const t=r.data;if(!o.options?.force){const e=a(t)?Object.values(k).map(l=>g(t.appName,l)):[p(t.type)],n=await C(e,i.cfnService);if(!n.success)return o.callbacks.onError?.(n.error),s(n.error)}if(a(t)){const e=await b(o,i,t);return e.success&&e.data.durationMs===void 0?u({...e.data,durationMs:Date.now()-f}):e}if(d(t))return v(o,i,t);const c=new Error(`Unsupported destroy target: ${o.target}`);return o.callbacks.onError?.(c),s(c)}export{T as destroy};
1
+ import{success as u,failure as s}from"@fjall/generator";import{isApplicationOperation as a,isOrganisationOperation as d,getApplicationStackName as g,getOrganisationStackName as p,APPLICATION_STACKS as k}from"../types/operations.js";import{createDeployServices as y}from"./serviceFactory.js";import{resolveOperation as w}from"./resolveOperation.js";import{destroyApplication as b}from"./application/applicationDestroy.js";import{destroyOrganisation as v}from"./organisation/organisationDestroy.js";import{checkActiveDeployments as C}from"./activeDeploymentGuard.js";async function T(o){const f=Date.now(),i=y({target:o.target,workingDirectory:o.workingDirectory,awsCredentials:o.awsCredentials,callbacks:o.callbacks,options:{verbose:o.options?.verbose,force:o.options?.force,cascade:o.options?.cascade,skipConfirmation:o.options?.skipConfirmation},orgConfig:o.orgConfig,identity:o.identity}),r=await w(o.target,o.workingDirectory);if(!r.success)return o.callbacks.onError?.(r.error),r;const t=r.data;if(!o.options?.force){const e=a(t)?Object.values(k).map(l=>g(t.appName,l)):[p(t.type)],n=await C(e,i.cfnService);if(!n.success)return o.callbacks.onError?.(n.error),s(n.error)}if(a(t)){const e=await b(o,i,t);return e.success&&e.data.durationMs===void 0?u({...e.data,durationMs:Date.now()-f}):e}if(d(t))return v(o,i,t);const c=new Error(`Unsupported destroy target: ${o.target}`);return o.callbacks.onError?.(c),s(c)}export{T as destroy};
@@ -1,16 +1,16 @@
1
1
  export { deploy } from "./deploy.js";
2
2
  export { destroy } from "./destroy.js";
3
- export { deployOrganisation } from "./organisationDeploy.js";
4
- export { destroyOrganisation } from "./organisationDestroy.js";
3
+ export { deployOrganisation } from "./organisation/organisationDeploy.js";
4
+ export { destroyOrganisation } from "./organisation/organisationDestroy.js";
5
5
  export { cleanupFailedStack, emptyS3Bucket, preEmptyStackBuckets, formatQuarantineSuspectedMessage, formatRetainedBucketsMessage, isQuarantineDetail, isRetainedBucketsDetail, PRE_EMPTY_TAG_KEYS, isCleanableState, SAFE_CLEANUP_STATES } from "./stackCleanup.js";
6
6
  export type { PreEmptyBucketsSummary, EmptyBucketOutcome } from "./stackCleanup.js";
7
- export type { CascadeDestroyAccountResult } from "./cascadeDestroyHelpers.js";
8
- export { partitionAccounts, buildRegionList, buildAccountRegionPairs, cascadeHomeRegion, cascadeOperationKey } from "./cascadeHelpers.js";
9
- export type { AccountRegionPair } from "./cascadeHelpers.js";
10
- export { projectScalarSummary, projectAccountRows } from "./cascadeSummary.js";
11
- export type { CascadeOutcomeResult, CascadeMemberOutcome, CascadePlatformOutcome, CascadeLedger, CascadeAccountRow, CascadePlatformRow, CascadeAccountProjection } from "./cascadeSummary.js";
12
- export { reconcileProviderAccounts, mergeReconciledProviderAccounts } from "./reconcileProviderAccounts.js";
13
- export type { ReconcileResult } from "./reconcileProviderAccounts.js";
7
+ export type { CascadeDestroyAccountResult } from "./organisation/cascadeDestroyHelpers.js";
8
+ export { partitionAccounts, buildRegionList, buildAccountRegionPairs, cascadeHomeRegion, cascadeOperationKey } from "./organisation/cascadeHelpers.js";
9
+ export type { AccountRegionPair } from "./organisation/cascadeHelpers.js";
10
+ export { projectScalarSummary, projectAccountRows } from "./organisation/cascadeSummary.js";
11
+ export type { CascadeOutcomeResult, CascadeMemberOutcome, CascadePlatformOutcome, CascadeLedger, CascadeAccountRow, CascadePlatformRow, CascadeAccountProjection } from "./organisation/cascadeSummary.js";
12
+ export { reconcileProviderAccounts, mergeReconciledProviderAccounts } from "./organisation/reconcileProviderAccounts.js";
13
+ export type { ReconcileResult } from "./organisation/reconcileProviderAccounts.js";
14
14
  export { decideNextTransition, reconcileTrailMigration, ORG_TRAIL_BUCKET_OUTPUT_KEY, TRAIL_BUCKET_OUTPUT_KEY, TRAIL_KEY_ARN_OUTPUT_KEY } from "./trailMigration/trailMigration.js";
15
15
  export type { MemberTrailFacts, TrailMigrationTransition, TrailMigrationOutcome } from "./trailMigration/trailMigration.js";
16
16
  export { decommissionMemberTrailStorage } from "./trailMigration/memberTrailCleanup.js";
@@ -27,13 +27,13 @@ export { restoreBucketPolicy, synthesiseEnforceSslDocument, ensureEnforceSsl } f
27
27
  export type { RestoreBucketPolicyInput, RestoreBucketPolicySuccess, RestoreBucketPolicyError } from "./unlock/restoreBucketPolicy.js";
28
28
  export { restoreAndReconcileQuarantinedBucket } from "./unlock/restoreAndReconcileQuarantinedBucket.js";
29
29
  export type { RestoreAndReconcileInput, RestoreAndReconcileReport, ReconcileOutcome } from "./unlock/restoreAndReconcileQuarantinedBucket.js";
30
- export { parseAccountsConfiguration, flattenAccountsToEnvironments, extractAllAccountNames, accountsConfigToOUTree, isStringArray, isAccountsConfig, isOuOnlyAccountBucket, OU_ONLY_ACCOUNT_BUCKETS } from "./accountsConfig.js";
31
- export type { AccountsConfig } from "./accountsConfig.js";
30
+ export { parseAccountsConfiguration, flattenAccountsToEnvironments, extractAllAccountNames, accountsConfigToOUTree, isStringArray, isAccountsConfig, isOuOnlyAccountBucket, OU_ONLY_ACCOUNT_BUCKETS } from "./organisation/accountsConfig.js";
31
+ export type { AccountsConfig } from "./organisation/accountsConfig.js";
32
32
  export type { DockerProvider, DockerProgressCallback, DockerServiceConfig, DockerBuildParams, DockerBuildResult, ECRInitParams, ECRInitResult, TagImagesParams, TagImagesResult, TagByDigestParams } from "./dockerInterface.js";
33
33
  export type { DomainDeployProvider, DomainConfig, DomainDeployResult } from "./domainInterface.js";
34
34
  export type { DeployServices } from "./serviceFactory.js";
35
- export type { DetectionResult } from "./detectionPipeline.js";
36
- export { runOpenNextBuild } from "./openNextBuild.js";
37
- export { runOrganisationSetup, ORG_SETUP_PHASES } from "./organisationSetup.js";
38
- export type { OrgSetupPhase, OrgSetupCallbacks, OrgSetupConfig, OrgSetupResult } from "./organisationSetup.js";
35
+ export type { DetectionResult } from "./application/detectionPipeline.js";
36
+ export { runOpenNextBuild } from "./application/openNextBuild.js";
37
+ export { runOrganisationSetup, ORG_SETUP_PHASES } from "./organisation/organisationSetup.js";
38
+ export type { OrgSetupPhase, OrgSetupCallbacks, OrgSetupConfig, OrgSetupResult } from "./organisation/organisationSetup.js";
39
39
  export * from "./builders/index.js";
@@ -1 +1 @@
1
- import{deploy as t}from"./deploy.js";import{destroy as n}from"./destroy.js";import{deployOrganisation as i}from"./organisationDeploy.js";import{destroyOrganisation as s}from"./organisationDestroy.js";import{cleanupFailedStack as m,emptyS3Bucket as p,preEmptyStackBuckets as l,formatQuarantineSuspectedMessage as f,formatRetainedBucketsMessage as T,isQuarantineDetail as x,isRetainedBucketsDetail as A,PRE_EMPTY_TAG_KEYS as S,isCleanableState as d,SAFE_CLEANUP_STATES as _}from"./stackCleanup.js";import{partitionAccounts as g,buildRegionList as O,buildAccountRegionPairs as R,cascadeHomeRegion as y,cascadeOperationKey as P}from"./cascadeHelpers.js";import{projectScalarSummary as k,projectAccountRows as B}from"./cascadeSummary.js";import{reconcileProviderAccounts as K,mergeReconciledProviderAccounts as N}from"./reconcileProviderAccounts.js";import{decideNextTransition as L,reconcileTrailMigration as M,ORG_TRAIL_BUCKET_OUTPUT_KEY as b,TRAIL_BUCKET_OUTPUT_KEY as Q,TRAIL_KEY_ARN_OUTPUT_KEY as v}from"./trailMigration/trailMigration.js";import{decommissionMemberTrailStorage as G}from"./trailMigration/memberTrailCleanup.js";import{unlockBucket as j}from"./unlock/unlockBucket.js";import{unlockQueue as H}from"./unlock/unlockQueue.js";import{triageBucketPolicy as w,isEnforceSslStatement as q}from"./unlock/bucketPolicyTriage.js";import{toResourcePolicyStatements as J}from"./unlock/toResourcePolicyStatements.js";import{restoreBucketPolicy as W,synthesiseEnforceSslDocument as X,ensureEnforceSsl as Z}from"./unlock/restoreBucketPolicy.js";import{restoreAndReconcileQuarantinedBucket as ee}from"./unlock/restoreAndReconcileQuarantinedBucket.js";import{parseAccountsConfiguration as te,flattenAccountsToEnvironments as re,extractAllAccountNames as ne,accountsConfigToOUTree as ce,isStringArray as ie,isAccountsConfig as ae,isOuOnlyAccountBucket as se,OU_ONLY_ACCOUNT_BUCKETS as ue}from"./accountsConfig.js";import{runOpenNextBuild as pe}from"./openNextBuild.js";import{runOrganisationSetup as fe,ORG_SETUP_PHASES as Te}from"./organisationSetup.js";export*from"./builders/index.js";export{Te as ORG_SETUP_PHASES,b as ORG_TRAIL_BUCKET_OUTPUT_KEY,ue as OU_ONLY_ACCOUNT_BUCKETS,S as PRE_EMPTY_TAG_KEYS,_ as SAFE_CLEANUP_STATES,Q as TRAIL_BUCKET_OUTPUT_KEY,v as TRAIL_KEY_ARN_OUTPUT_KEY,ce as accountsConfigToOUTree,R as buildAccountRegionPairs,O as buildRegionList,y as cascadeHomeRegion,P as cascadeOperationKey,m as cleanupFailedStack,L as decideNextTransition,G as decommissionMemberTrailStorage,t as deploy,i as deployOrganisation,n as destroy,s as destroyOrganisation,p as emptyS3Bucket,Z as ensureEnforceSsl,ne as extractAllAccountNames,re as flattenAccountsToEnvironments,f as formatQuarantineSuspectedMessage,T as formatRetainedBucketsMessage,ae as isAccountsConfig,d as isCleanableState,q as isEnforceSslStatement,se as isOuOnlyAccountBucket,x as isQuarantineDetail,A as isRetainedBucketsDetail,ie as isStringArray,N as mergeReconciledProviderAccounts,te as parseAccountsConfiguration,g as partitionAccounts,l as preEmptyStackBuckets,B as projectAccountRows,k as projectScalarSummary,K as reconcileProviderAccounts,M as reconcileTrailMigration,ee as restoreAndReconcileQuarantinedBucket,W as restoreBucketPolicy,pe as runOpenNextBuild,fe as runOrganisationSetup,X as synthesiseEnforceSslDocument,J as toResourcePolicyStatements,w as triageBucketPolicy,j as unlockBucket,H as unlockQueue};
1
+ import{deploy as t}from"./deploy.js";import{destroy as n}from"./destroy.js";import{deployOrganisation as i}from"./organisation/organisationDeploy.js";import{destroyOrganisation as s}from"./organisation/organisationDestroy.js";import{cleanupFailedStack as m,emptyS3Bucket as p,preEmptyStackBuckets as l,formatQuarantineSuspectedMessage as f,formatRetainedBucketsMessage as T,isQuarantineDetail as x,isRetainedBucketsDetail as A,PRE_EMPTY_TAG_KEYS as S,isCleanableState as d,SAFE_CLEANUP_STATES as _}from"./stackCleanup.js";import{partitionAccounts as g,buildRegionList as O,buildAccountRegionPairs as R,cascadeHomeRegion as y,cascadeOperationKey as P}from"./organisation/cascadeHelpers.js";import{projectScalarSummary as k,projectAccountRows as B}from"./organisation/cascadeSummary.js";import{reconcileProviderAccounts as K,mergeReconciledProviderAccounts as N}from"./organisation/reconcileProviderAccounts.js";import{decideNextTransition as L,reconcileTrailMigration as M,ORG_TRAIL_BUCKET_OUTPUT_KEY as b,TRAIL_BUCKET_OUTPUT_KEY as Q,TRAIL_KEY_ARN_OUTPUT_KEY as v}from"./trailMigration/trailMigration.js";import{decommissionMemberTrailStorage as G}from"./trailMigration/memberTrailCleanup.js";import{unlockBucket as j}from"./unlock/unlockBucket.js";import{unlockQueue as H}from"./unlock/unlockQueue.js";import{triageBucketPolicy as w,isEnforceSslStatement as q}from"./unlock/bucketPolicyTriage.js";import{toResourcePolicyStatements as J}from"./unlock/toResourcePolicyStatements.js";import{restoreBucketPolicy as W,synthesiseEnforceSslDocument as X,ensureEnforceSsl as Z}from"./unlock/restoreBucketPolicy.js";import{restoreAndReconcileQuarantinedBucket as ee}from"./unlock/restoreAndReconcileQuarantinedBucket.js";import{parseAccountsConfiguration as te,flattenAccountsToEnvironments as re,extractAllAccountNames as ne,accountsConfigToOUTree as ce,isStringArray as ie,isAccountsConfig as ae,isOuOnlyAccountBucket as se,OU_ONLY_ACCOUNT_BUCKETS as ue}from"./organisation/accountsConfig.js";import{runOpenNextBuild as pe}from"./application/openNextBuild.js";import{runOrganisationSetup as fe,ORG_SETUP_PHASES as Te}from"./organisation/organisationSetup.js";export*from"./builders/index.js";export{Te as ORG_SETUP_PHASES,b as ORG_TRAIL_BUCKET_OUTPUT_KEY,ue as OU_ONLY_ACCOUNT_BUCKETS,S as PRE_EMPTY_TAG_KEYS,_ as SAFE_CLEANUP_STATES,Q as TRAIL_BUCKET_OUTPUT_KEY,v as TRAIL_KEY_ARN_OUTPUT_KEY,ce as accountsConfigToOUTree,R as buildAccountRegionPairs,O as buildRegionList,y as cascadeHomeRegion,P as cascadeOperationKey,m as cleanupFailedStack,L as decideNextTransition,G as decommissionMemberTrailStorage,t as deploy,i as deployOrganisation,n as destroy,s as destroyOrganisation,p as emptyS3Bucket,Z as ensureEnforceSsl,ne as extractAllAccountNames,re as flattenAccountsToEnvironments,f as formatQuarantineSuspectedMessage,T as formatRetainedBucketsMessage,ae as isAccountsConfig,d as isCleanableState,q as isEnforceSslStatement,se as isOuOnlyAccountBucket,x as isQuarantineDetail,A as isRetainedBucketsDetail,ie as isStringArray,N as mergeReconciledProviderAccounts,te as parseAccountsConfiguration,g as partitionAccounts,l as preEmptyStackBuckets,B as projectAccountRows,k as projectScalarSummary,K as reconcileProviderAccounts,M as reconcileTrailMigration,ee as restoreAndReconcileQuarantinedBucket,W as restoreBucketPolicy,pe as runOpenNextBuild,fe as runOrganisationSetup,X as synthesiseEnforceSslDocument,J as toResourcePolicyStatements,w as triageBucketPolicy,j as unlockBucket,H as unlockQueue};
@@ -1,6 +1,6 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import { ConfigError } from "../types/errors/ServiceError.js";
3
- import type { OUTree } from "../aws/organisations/types.js";
2
+ import { ConfigError } from "../../types/errors/ServiceError.js";
3
+ import type { OUTree } from "../../aws/organisations/types.js";
4
4
  export type AccountsConfig = {
5
5
  readonly [key: string]: string | readonly string[] | AccountsConfig;
6
6
  };
@@ -0,0 +1 @@
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};
@@ -4,10 +4,10 @@
4
4
  * Mirrors the deployCascadeAccount() pattern from cascadeHelpers.ts but
5
5
  * for destroy operations. No bootstrap step (unlike deploy).
6
6
  */
7
- import type { DestroyParams } from "../types/params.js";
8
- import type { DeployCallbacks } from "../types/callbacks.js";
9
- import type { OrganisationOperation } from "../types/operations.js";
10
- import type { DeployServices } from "./serviceFactory.js";
7
+ import type { DestroyParams } from "../../types/params.js";
8
+ import type { DeployCallbacks } from "../../types/callbacks.js";
9
+ import type { OrganisationOperation } from "../../types/operations.js";
10
+ import type { DeployServices } from "../serviceFactory.js";
11
11
  export interface CascadeDestroyAccountResult {
12
12
  accountName: string;
13
13
  accountId: string;
@@ -0,0 +1 @@
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/config/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,10 +1,10 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployParams } from "../types/params.js";
3
- import type { OrgConfig } from "../types/orgConfig.js";
4
- import type { DeployCallbacks } from "../types/callbacks.js";
5
- import type { OrganisationOperation } from "../types/operations.js";
6
- import type { DeployServices } from "./serviceFactory.js";
7
- import type { DomainDeployProvider } from "./domainInterface.js";
2
+ import type { DeployParams } from "../../types/params.js";
3
+ import type { OrgConfig } from "../../types/config/orgConfig.js";
4
+ import type { DeployCallbacks } from "../../types/callbacks.js";
5
+ import type { OrganisationOperation } from "../../types/operations.js";
6
+ import type { DeployServices } from "../serviceFactory.js";
7
+ import type { DomainDeployProvider } from "../domainInterface.js";
8
8
  import type { ProviderAccount } from "@fjall/util/config";
9
9
  /**
10
10
  * Max member accounts deployed/destroyed in parallel during a cascade. Each
@@ -52,7 +52,7 @@ export declare function cascadeOperationKey(accountName: string, region: string)
52
52
  * Build all account x region pairs for parallel cascade fan-out.
53
53
  */
54
54
  export declare function buildAccountRegionPairs(accounts: ProviderAccount[], regions: string[]): AccountRegionPair[];
55
- export { buildCascadeRoleArn } from "./contextHelpers.js";
55
+ export { buildCascadeRoleArn } from "../contextHelpers.js";
56
56
  /** A single pre-flight probe failure: the cascade role could not be assumed. */
57
57
  export interface CascadeRoleProbeFailure {
58
58
  accountId: string;
@@ -0,0 +1 @@
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/config/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};
@@ -15,7 +15,7 @@
15
15
  * stay `*Deployed`, carrying destroyed counts for a destroy). Adding a new summary
16
16
  * field means extending the ledger + one projector; every consumer inherits it.
17
17
  */
18
- import type { CascadeDeploymentResult } from "../types/events.js";
18
+ import type { CascadeDeploymentResult } from "../../types/events.js";
19
19
  export type CascadeOutcomeResult = "succeeded" | "skipped" | "failed";
20
20
  /** One account×region pair's outcome. */
21
21
  export interface CascadeMemberOutcome {
@@ -1,8 +1,8 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { DeployParams, DeployResult } from "../types/params.js";
3
- import type { OrganisationOperation } from "../types/operations.js";
4
- import type { DeployServices } from "./serviceFactory.js";
5
- export type { OrgDetailsForSynth } from "./organisationDeploy/orgContext.js";
2
+ import type { DeployParams, DeployResult } from "../../types/params.js";
3
+ import type { OrganisationOperation } from "../../types/operations.js";
4
+ import type { DeployServices } from "../serviceFactory.js";
5
+ export type { OrgDetailsForSynth } from "../organisationDeploy/orgContext.js";
6
6
  /**
7
7
  * Organisation deployment orchestration.
8
8
  *
@@ -0,0 +1 @@
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};
@@ -11,9 +11,9 @@
11
11
  * (per engine/consumer boundary).
12
12
  */
13
13
  import { type Result } from "@fjall/generator";
14
- import type { DestroyParams, DestroyResult } from "../types/params.js";
15
- import type { OrganisationOperation } from "../types/operations.js";
16
- import type { DeployServices } from "./serviceFactory.js";
14
+ import type { DestroyParams, DestroyResult } from "../../types/params.js";
15
+ import type { OrganisationOperation } from "../../types/operations.js";
16
+ import type { DeployServices } from "../serviceFactory.js";
17
17
  /**
18
18
  * Destroy organisation infrastructure with cascade.
19
19
  *
@@ -0,0 +1,3 @@
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,6 +1,6 @@
1
1
  import { type Result } from "@fjall/generator";
2
- import type { AwsProvider } from "../aws/AwsProvider.js";
3
- import type { OUTree, AccountInfo } from "../aws/organisations/types.js";
2
+ import type { AwsProvider } from "../../aws/AwsProvider.js";
3
+ import type { OUTree, AccountInfo } from "../../aws/organisations/types.js";
4
4
  /**
5
5
  * Execution-order tuple of org-setup phases — single source of truth for the
6
6
  * OrgSetupPhase union. The engine's hand-written phase blocks are tethered to
@@ -0,0 +1 @@
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};