@fjall/deploy-core 7.3.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@
9
9
  "package.json",
10
10
  "tsconfig.json"
11
11
  ],
12
- "hash": "3b49ad912fee50e6b73c088c82ed14e503f3ca322b4e1fa0026228af9848cae8",
12
+ "hash": "7ea3e98f341b69eff0b080c0b37d808ee3944922e33d5f9d86f28b1204e988fb",
13
13
  "files": {
14
14
  "src/__test-utils__/awsMockHelpers.ts": "fea371c1b1869678",
15
15
  "src/__test-utils__/index.ts": "e03cf35821157dc4",
@@ -47,16 +47,16 @@
47
47
  "src/aws/utils/awsErrorHandler.ts": "b9dc3afe3fabacf3",
48
48
  "src/aws/utils/cloudformationEventHelpers.ts": "c2850420073796dd",
49
49
  "src/aws/utils/cloudformationEventTypes.ts": "ab8c4927fb9b04cb",
50
- "src/aws/utils/cloudformationEvents.ts": "5e21a29ffcd9ffca",
50
+ "src/aws/utils/cloudformationEvents.ts": "b4efd9a024f3c93d",
51
51
  "src/aws/utils/index.ts": "97c9ac921f2c0ad2",
52
52
  "src/aws/utils/regions.ts": "3a5191f4d2242a60",
53
53
  "src/aws/utils/stackStatus.ts": "55fb23ba5514bc13",
54
54
  "src/events/index.ts": "e55c022f0d9013e6",
55
55
  "src/index.ts": "138bc00a3dedc54f",
56
56
  "src/orchestration/activeDeploymentGuard.ts": "e8dde742c00ea728",
57
- "src/orchestration/application/applicationDeploy.ts": "2da75f7e60474a5a",
57
+ "src/orchestration/application/applicationDeploy.ts": "04799f21f18d40c8",
58
58
  "src/orchestration/application/applicationDeployHelpers.ts": "a8023472f95d3f05",
59
- "src/orchestration/application/applicationDestroy.ts": "1044d686e026b509",
59
+ "src/orchestration/application/applicationDestroy.ts": "38b8ecf69c85d884",
60
60
  "src/orchestration/application/approvalGate.ts": "e4dee819c94fd15c",
61
61
  "src/orchestration/application/artefactCollection.ts": "b77848c922765d30",
62
62
  "src/orchestration/application/assemblyIntegrity.ts": "6399169e3062118c",
@@ -100,6 +100,7 @@
100
100
  "src/orchestration/builders/staticSiteBuilder.ts": "a0403be58052310f",
101
101
  "src/orchestration/contextHelpers.ts": "b38af2503a974f16",
102
102
  "src/orchestration/deploy.ts": "4f86b86e797bc04f",
103
+ "src/orchestration/deployEnvironment.ts": "acfddd575949b7ca",
103
104
  "src/orchestration/destroy.ts": "b6da154984e63a8d",
104
105
  "src/orchestration/dockerBuildParamsKeys.ts": "ac3d6ecd24d68792",
105
106
  "src/orchestration/dockerInterface.ts": "f2efc8a98d3b3d65",
@@ -123,8 +124,8 @@
123
124
  "src/orchestration/index.ts": "e97a95546428a69a",
124
125
  "src/orchestration/manifestSecretParser.ts": "c5576bd88a3652f8",
125
126
  "src/orchestration/organisation/accountsConfig.ts": "a55a3306564e7b2f",
126
- "src/orchestration/organisation/cascadeDestroyHelpers.ts": "ccebbcfac5d1c374",
127
- "src/orchestration/organisation/cascadeHelpers.ts": "bc8af3d99f15a655",
127
+ "src/orchestration/organisation/cascadeDestroyHelpers.ts": "46ca8e144ea76db2",
128
+ "src/orchestration/organisation/cascadeHelpers.ts": "d62c4c3b385f9c17",
128
129
  "src/orchestration/organisation/cascadeSummary.ts": "335be9649cea0626",
129
130
  "src/orchestration/organisation/organisationDeploy.ts": "f3fad626230d8a16",
130
131
  "src/orchestration/organisation/organisationDestroy.ts": "06076a7e76345854",
@@ -133,7 +134,7 @@
133
134
  "src/orchestration/organisationDeploy/cascadeExecution.ts": "ebc32ef8857d8037",
134
135
  "src/orchestration/organisationDeploy/infraSteps.ts": "53880cef5020dd04",
135
136
  "src/orchestration/organisationDeploy/orgCascadeDeploy.ts": "97a85277580be283",
136
- "src/orchestration/organisationDeploy/orgContext.ts": "14ff392c172575ab",
137
+ "src/orchestration/organisationDeploy/orgContext.ts": "635f429a7cbb7c32",
137
138
  "src/orchestration/organisationDeploy/resolveCascadeAccounts.ts": "1804c62e365a881a",
138
139
  "src/orchestration/organisationDeploy/singleComponentDeploy.ts": "3e35f43f3a59097c",
139
140
  "src/orchestration/organisationDeploy/trailReconciliation.ts": "7aefb6417c77280b",
@@ -150,7 +151,7 @@
150
151
  "src/orchestration/remediation/remediationJournal.ts": "9c37f66a07ce9912",
151
152
  "src/orchestration/remediation/removalUpdate.ts": "805be54eb0ac1623",
152
153
  "src/orchestration/remediation/retainFlip.ts": "8799a03380cb8f45",
153
- "src/orchestration/resolveOperation.ts": "9bcd66ba59808104",
154
+ "src/orchestration/resolveOperation.ts": "84a9f425939fad63",
154
155
  "src/orchestration/restart/restartApplication.ts": "08866c681f84c1ed",
155
156
  "src/orchestration/restart/secretsDrift.ts": "53b91db6f8f7f04d",
156
157
  "src/orchestration/secretArnResolver.ts": "20041aca6eb7e762",
@@ -178,20 +179,20 @@
178
179
  "src/services/application/applicationStackHelpers.ts": "04e1758b97ffc5fc",
179
180
  "src/services/application/index.ts": "ee810be803dfda9c",
180
181
  "src/services/index.ts": "8c441c1b37540650",
181
- "src/services/infrastructure/CdkArgumentBuilder.ts": "e1b866b72e619bf5",
182
+ "src/services/infrastructure/CdkArgumentBuilder.ts": "0427db7f4a0add33",
182
183
  "src/services/infrastructure/CdkCommandRunner.ts": "427a9c1171902e42",
183
184
  "src/services/infrastructure/CdkErrorFormatter.ts": "e2e017136108a5d1",
184
- "src/services/infrastructure/CdkEventMonitoring.ts": "2b0ecd84f76c54c1",
185
- "src/services/infrastructure/CdkOutputAnalyser.ts": "37114ecae45d7017",
185
+ "src/services/infrastructure/CdkEventMonitoring.ts": "f172272948a5db40",
186
+ "src/services/infrastructure/CdkOutputAnalyser.ts": "2e7cb06b6554de84",
186
187
  "src/services/infrastructure/CdkOutputParser.ts": "eed9f3475324118e",
187
188
  "src/services/infrastructure/CdkProcessManager.ts": "f68ae4e44adee2fa",
188
- "src/services/infrastructure/CdkService.ts": "9b5883d5362be9b0",
189
+ "src/services/infrastructure/CdkService.ts": "9494e7b64b3dd9f3",
189
190
  "src/services/infrastructure/CdkServiceTypes.ts": "c4f3075238952728",
190
191
  "src/services/infrastructure/CfnRegistryService.ts": "bd4f57d97982cdda",
191
192
  "src/services/infrastructure/CloudFormationService.ts": "82ce03614feae2db",
192
193
  "src/services/infrastructure/EcrImageInspectorService.ts": "e784440b5680df74",
193
194
  "src/services/infrastructure/EcsService.ts": "caa79dd5fd7ada9b",
194
- "src/services/infrastructure/EcsServiceResolver.ts": "bad8ea4dc59c64a8",
195
+ "src/services/infrastructure/EcsServiceResolver.ts": "4d548cd42e746feb",
195
196
  "src/services/infrastructure/ICdkProcessManager.ts": "7e7bfe7dacc999f6",
196
197
  "src/services/infrastructure/LambdaService.ts": "5b21e34185e77a4c",
197
198
  "src/services/infrastructure/SsmParameterMetadataService.ts": "3ff0bb42219cec3d",
@@ -204,7 +205,7 @@
204
205
  "src/services/infrastructure/dependencyIntegrityGate.ts": "c984bb1620f73f38",
205
206
  "src/services/infrastructure/index.ts": "c45e43711bf64fa9",
206
207
  "src/services/infrastructure/infraTypecheckPreflight.ts": "09e423a8283e3b8b",
207
- "src/services/infrastructure/transientNetworkRecovery.ts": "44a9333ce963bf50",
208
+ "src/services/infrastructure/transientNetworkRecovery.ts": "264b9a16fea86869",
208
209
  "src/services/supporting/CdkContextBuilder.ts": "dee16f4302f7312a",
209
210
  "src/services/supporting/TemplateHashService.ts": "9fd5351f90b5cefb",
210
211
  "src/services/supporting/helpers.ts": "81c0ba7bfdd99366",
@@ -229,7 +230,7 @@
229
230
  "src/types/deployEventParse.ts": "16bc2371fb747d96",
230
231
  "src/types/deployEventTypeGuards.ts": "731c4e5761b13fca",
231
232
  "src/types/deployment/DeploymentServiceTypes.ts": "ba257f2e37013afd",
232
- "src/types/deployment/DeploymentTypes.ts": "60de9197195ba96e",
233
+ "src/types/deployment/DeploymentTypes.ts": "795eb2bccf126f59",
233
234
  "src/types/deployment/cloudformation.ts": "5a0c51e263827943",
234
235
  "src/types/deployment/index.ts": "4bdffd195379a05d",
235
236
  "src/types/deployment/parallel.ts": "c06e3ac55fb39eb7",
@@ -256,9 +257,9 @@
256
257
  "src/util/sleepAbortable.ts": "f9cac3bf8a79a476",
257
258
  "src/version.ts": "1811a4475823b27e",
258
259
  "../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
259
- "package.json": "0219e8b7a4261ea4",
260
+ "package.json": "4b33bf9951961358",
260
261
  "tsconfig.json": "1929cf3f99fe9b1e"
261
262
  },
262
- "outputHash": "b4f80d357531a4bfbe5eb599bbb0a4ebc9664a3cb02d53af542581eb76e7ec03",
263
- "outputFileCount": 481
263
+ "outputHash": "7cb96ff606aa8fa1b52ebf8be946ab46dd466be575596af0bd00d1ec1bef16c9",
264
+ "outputFileCount": 483
264
265
  }
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 240 files minified at 2026-08-06T23:10:57.233Z
1
+ 241 files minified at 2026-08-09T06:27:58.486Z
@@ -1 +1 @@
1
- var p=Object.defineProperty;var v=(S,t)=>p(S,"name",{value:t,configurable:!0});import{logger as c}from"@fjall/util/logger";import{getErrorMessage as m,sleep as d}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as M}from"@fjall/util/aws";import{isResourceEvent as N,STACK_NOT_FOUND_PATTERN as W,CDK_NO_STACKS_MATCH as U}from"@fjall/util/aws";import{CF_STACK_RESOURCE_TYPE as C}from"./cloudformationEventTypes.js";import{isTerminalState as w,isSuccessState as E,IpamConcurrencyTracker as F,pollStackEvents as L,handleStackCompletion as T,fetchStackStatus as R,fetchCurrentResources as A,EVENT_CUTOFF_SKEW_MS as I}from"./cloudformationEventHelpers.js";class K{static{v(this,"CloudFormationEventMonitor")}aws;seenEventIds=new Set;isMonitoring=!1;pollInterval=null;activeNestedStacks=new Map;failureReasons=new Map;eventHistory=new Map;eventLogger=null;failureAnalyser;eventLogWriterFactory;onFailureAnalysis;lastAnalysis=null;maxHistorySize=1e3;maxSeenEventIds=1e4;ipamTracker=new F;eventCutoffMs=0;tokenLatchCutoffMs=0;constructor(t,e){this.aws=t,this.failureAnalyser=e?.failureAnalyser??null,this.eventLogWriterFactory=e?.eventLogWriterFactory,this.onFailureAnalysis=e?.onFailureAnalysis}enableLogging(t,e,r,a){this.eventLogWriterFactory&&(this.eventLogger=this.eventLogWriterFactory(t,e,r,a))}async startMonitoring(t,e,r){if(this.isMonitoring){c.debug("CloudFormation","startMonitoring SKIPPED - already monitoring",{stackName:t});return}c.debug("CloudFormation","startMonitoring STARTED",{stackName:t}),this.isMonitoring=!0,this.seenEventIds.clear(),this.eventHistory.clear();const a=Date.now();this.eventCutoffMs=a-I,this.tokenLatchCutoffMs=a;try{await this.pollEvents(t,()=>{})}catch(o){const h=m(o);h.includes(M)||c.debug("CloudFormation","Initial poll failed",{error:h})}let s=5e3;const n=1e4;let g=0,l=0;const u=v(async()=>{if(this.isMonitoring)try{const o=await this.pollEvents(t,e);g++,o==="throttled"?(l++,s=Math.min(3e4,5e3*Math.pow(2,l-1))):(l=0,g>20&&s<n?s=Math.min(n,s+1e3):g<=20&&(s=5e3)),o===!0?await this.handleStackComplete(t,r):this.pollInterval=setTimeout(u,s)}catch(o){c.debug("CloudFormation","Polling iteration error (continuing)",{error:m(o)}),this.pollInterval=setTimeout(u,s)}},"poll");this.pollInterval=setTimeout(u,s)}stopMonitoring(){c.debug("CloudFormation","stopMonitoring called",{wasMonitoring:this.isMonitoring,seenEventCount:this.seenEventIds.size}),this.isMonitoring=!1,this.pollInterval&&(clearTimeout(this.pollInterval),this.pollInterval=null),this.cleanup()}cleanup(){if(this.activeNestedStacks.clear(),this.failureReasons.clear(),this.eventHistory.clear(),this.ipamTracker.clear(),this.seenEventIds.size>this.maxSeenEventIds){const t=Array.from(this.seenEventIds),e=Math.floor(this.maxSeenEventIds/2);this.seenEventIds=new Set(t.slice(-e))}this.eventLogger&&(this.eventLogger=null)}async handleStackComplete(t,e){const r=new Map(this.failureReasons),a=new Map(this.eventHistory),s=this.eventLogger;this.stopMonitoring();const n=await T(this.aws,t,r,s,this.failureAnalyser,a);this.lastAnalysis=n.analysis,n.analysis&&this.onFailureAnalysis&&this.onFailureAnalysis(n.analysis),e&&e(n.success,n.failureMessage)}getResourceHistory(t){return this.eventHistory.get(t)||[]}getEventHistory(){return new Map(this.eventHistory)}getFailureAnalysis(){return this.lastAnalysis}getFirstFailureReason(){return this.failureReasons.size>0?Array.from(this.failureReasons.values())[0]??null:null}getEventLogger(){return this.eventLogger}getEventLogPath(){return this.eventLogger?.getLogPath()||null}getLogSummary(){return this.eventLogger?.getLogSummary()||null}async waitForStackComplete(t,e={}){const{timeout:r=1800*1e3,pollInterval:a=2e3,onResourceUpdate:s,onStackComplete:n}=e,g=Date.now();let l,u=!1,o=!1,h;c.debug("CloudFormation","waitForStackComplete called",{stackName:t,timeout:r,pollInterval:a,hasOnResourceUpdate:!!s}),await this.startMonitoring(t,i=>{s&&s(i),i.resourceType===C&&i.logicalId===t&&(l=i.status,w(i.status)&&(u=!0,o=E(i.status),o||(h=this.getFirstFailureReason()||i.statusReason||"Stack operation failed")))},(i,f)=>{n&&n(i,f)});try{let i=!1,f=!1;for(;!u&&this.isMonitoring&&Date.now()-g<r;)await d(a),!i&&Date.now()-g>3e4&&!f&&(f=!0,await this.getStackStatus(t)||c.debug("CloudFormation","Stack not found after 30s, continuing to wait (CDK may be uploading assets)",{stackName:t})),l&&(i=!0);if(this.stopMonitoring(),!u){if(h)return{success:!1,status:"FAILED",failureReason:h,logPath:this.getLogSummary()||void 0};const y=await this.getStackStatus(t);return{success:!1,status:y?.status||"UNKNOWN",failureReason:`Deployment timed out after ${r/1e3} seconds. Stack status: ${y?.status||"UNKNOWN"}`,logPath:this.getLogSummary()||void 0}}return{success:o,status:l,failureReason:h,logPath:this.getLogSummary()||void 0}}catch(i){return this.stopMonitoring(),{success:!1,failureReason:`Monitoring error: ${m(i)}`,logPath:this.getLogSummary()||void 0}}}pollContext(){return{aws:this.aws,seenEventIds:this.seenEventIds,activeNestedStacks:this.activeNestedStacks,failureReasons:this.failureReasons,eventHistory:this.eventHistory,maxHistorySize:this.maxHistorySize,ipamTracker:this.ipamTracker,eventLogger:this.eventLogger,eventCutoffMs:this.eventCutoffMs,tokenLatchCutoffMs:this.tokenLatchCutoffMs}}async pollEvents(t,e){return L(this.pollContext(),t,e)}async getStackStatus(t,e){return R(this.aws,t,e)}async getCurrentResources(t){return A(this.aws,t)}}export{U as CDK_NO_STACKS_MATCH,K as CloudFormationEventMonitor,W as STACK_NOT_FOUND_PATTERN,N as isResourceEvent};
1
+ var w=Object.defineProperty;var y=(m,t)=>w(m,"name",{value:t,configurable:!0});import{logger as h}from"@fjall/util/logger";import{getErrorMessage as S,sleep as E}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as F}from"@fjall/util/aws";import{isResourceEvent as U,STACK_NOT_FOUND_PATTERN as z,CDK_NO_STACKS_MATCH as $}from"@fjall/util/aws";import{CF_STACK_RESOURCE_TYPE as L}from"./cloudformationEventTypes.js";import{isTerminalState as v,isSuccessState as C,IpamConcurrencyTracker as R,pollStackEvents as T,handleStackCompletion as A,fetchStackStatus as k,fetchCurrentResources as I,EVENT_CUTOFF_SKEW_MS as H}from"./cloudformationEventHelpers.js";class O{static{y(this,"CloudFormationEventMonitor")}aws;seenEventIds=new Set;isMonitoring=!1;pollInterval=null;activeNestedStacks=new Map;failureReasons=new Map;eventHistory=new Map;eventLogger=null;failureAnalyser;eventLogWriterFactory;onFailureAnalysis;lastAnalysis=null;maxHistorySize=1e3;maxSeenEventIds=1e4;ipamTracker=new R;eventCutoffMs=0;tokenLatchCutoffMs=0;constructor(t,e){this.aws=t,this.failureAnalyser=e?.failureAnalyser??null,this.eventLogWriterFactory=e?.eventLogWriterFactory,this.onFailureAnalysis=e?.onFailureAnalysis}enableLogging(t,e,a,r){this.eventLogWriterFactory&&(this.eventLogger=this.eventLogWriterFactory(t,e,a,r))}async startMonitoring(t,e,a){if(this.isMonitoring){h.debug("CloudFormation","startMonitoring SKIPPED - already monitoring",{stackName:t});return}h.debug("CloudFormation","startMonitoring STARTED",{stackName:t}),this.isMonitoring=!0,this.seenEventIds.clear(),this.eventHistory.clear();const r=Date.now();this.eventCutoffMs=r-H,this.tokenLatchCutoffMs=r;try{await this.pollEvents(t,()=>{})}catch(o){const c=S(o);c.includes(F)||h.debug("CloudFormation","Initial poll failed",{error:c})}let s=5e3;const n=1e4;let g=0,l=0;const u=y(async()=>{if(this.isMonitoring)try{const o=await this.pollEvents(t,e);g++,o==="throttled"?(l++,s=Math.min(3e4,5e3*Math.pow(2,l-1))):(l=0,g>20&&s<n?s=Math.min(n,s+1e3):g<=20&&(s=5e3)),o===!0?await this.handleStackComplete(t,a):this.pollInterval=setTimeout(u,s)}catch(o){h.debug("CloudFormation","Polling iteration error (continuing)",{error:S(o)}),this.pollInterval=setTimeout(u,s)}},"poll");this.pollInterval=setTimeout(u,s)}stopMonitoring(){h.debug("CloudFormation","stopMonitoring called",{wasMonitoring:this.isMonitoring,seenEventCount:this.seenEventIds.size}),this.isMonitoring=!1,this.pollInterval&&(clearTimeout(this.pollInterval),this.pollInterval=null),this.cleanup()}cleanup(){if(this.activeNestedStacks.clear(),this.failureReasons.clear(),this.eventHistory.clear(),this.ipamTracker.clear(),this.seenEventIds.size>this.maxSeenEventIds){const t=Array.from(this.seenEventIds),e=Math.floor(this.maxSeenEventIds/2);this.seenEventIds=new Set(t.slice(-e))}this.eventLogger&&(this.eventLogger=null)}async handleStackComplete(t,e){const a=new Map(this.failureReasons),r=new Map(this.eventHistory),s=this.eventLogger;this.stopMonitoring();const n=await A(this.aws,t,a,s,this.failureAnalyser,r);this.lastAnalysis=n.analysis,n.analysis&&this.onFailureAnalysis&&this.onFailureAnalysis(n.analysis),e&&e(n.success,n.failureMessage)}getResourceHistory(t){return this.eventHistory.get(t)||[]}getEventHistory(){return new Map(this.eventHistory)}getFailureAnalysis(){return this.lastAnalysis}getFirstFailureReason(){return this.failureReasons.size>0?Array.from(this.failureReasons.values())[0]??null:null}getEventLogger(){return this.eventLogger}getEventLogPath(){return this.eventLogger?.getLogPath()||null}getLogSummary(){return this.eventLogger?.getLogSummary()||null}async waitForStackComplete(t,e={}){const{timeout:a=1800*1e3,pollInterval:r=2e3,onResourceUpdate:s,onStackComplete:n}=e,g=Date.now();let l,u=!1,o=!1,c;h.debug("CloudFormation","waitForStackComplete called",{stackName:t,timeout:a,pollInterval:r,hasOnResourceUpdate:!!s}),await this.startMonitoring(t,i=>{s&&s(i),i.resourceType===L&&i.logicalId===t&&(l=i.status,v(i.status)&&(u=!0,o=C(i.status),o||(c=this.getFirstFailureReason()||i.statusReason||"Stack operation failed")))},(i,d)=>{n&&n(i,d)});try{let i=!1,d=!1;for(;!u&&this.isMonitoring&&Date.now()-g<a;)await E(r),!i&&Date.now()-g>3e4&&!d&&(d=!0,await this.getStackStatus(t)||h.debug("CloudFormation","Stack not found after 30s, continuing to wait (CDK may be uploading assets)",{stackName:t})),l&&(i=!0);if(this.stopMonitoring(),!u){if(c)return{success:!1,status:"FAILED",failureReason:c,logPath:this.getLogSummary()||void 0};const f=(await this.getStackStatus(t))?.status;if(f!==void 0&&v(f)){const p=C(f);return{success:p,status:f,failureReason:p?void 0:this.getFirstFailureReason()||"Stack operation failed",logPath:this.getLogSummary()||void 0}}return{success:!1,status:f||"UNKNOWN",failureReason:`Stopped monitoring after ${a/1e3} seconds; the CloudFormation operation may still be running. Stack status: ${f||"UNKNOWN"}`,logPath:this.getLogSummary()||void 0}}return{success:o,status:l,failureReason:c,logPath:this.getLogSummary()||void 0}}catch(i){return this.stopMonitoring(),{success:!1,failureReason:`Monitoring error: ${S(i)}`,logPath:this.getLogSummary()||void 0}}}pollContext(){return{aws:this.aws,seenEventIds:this.seenEventIds,activeNestedStacks:this.activeNestedStacks,failureReasons:this.failureReasons,eventHistory:this.eventHistory,maxHistorySize:this.maxHistorySize,ipamTracker:this.ipamTracker,eventLogger:this.eventLogger,eventCutoffMs:this.eventCutoffMs,tokenLatchCutoffMs:this.tokenLatchCutoffMs}}async pollEvents(t,e){return T(this.pollContext(),t,e)}async getStackStatus(t,e){return k(this.aws,t,e)}async getCurrentResources(t){return I(this.aws,t)}}export{$ as CDK_NO_STACKS_MATCH,O as CloudFormationEventMonitor,z as STACK_NOT_FOUND_PATTERN,U as isResourceEvent};
@@ -1 +1 @@
1
- var De=Object.defineProperty;var D=(e,n)=>De(e,"name",{value:n,configurable:!0});import{join as ge}from"path";import{CloudFormationClient as Re}from"@aws-sdk/client-cloudformation";import{EC2Client as Ce}from"@aws-sdk/client-ec2";import{success as I,failure as u}from"@fjall/generator";import{logger as F}from"@fjall/util/logger";import{ImageTagSchema as Ie,getErrorMessage as Y,imageTagParameterName as Oe,maskSensitiveOutput as S}from"@fjall/util";import{parseDockerDeclarationsFromManifest as Ae}from"@fjall/util/manifest";import{stubCallerIdentity as Le}from"../../types/deployment/index.js";import{getApplicationDeployOrder as Be,getApplicationStackName as O,getApplicationStepName as fe,getApplicationStepId as pe,APPLICATION_STACKS as C}from"../../types/operations.js";import{CdkContextBuilder as Me}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as $e,buildCapacityIdentityContext as xe,bootstrapOrFail as Fe}from"../contextHelpers.js";import{buildBootstrapTagsIfResolved as je}from"../bootstrapTags.js";import{hasOrganisationTierAccount as Ge}from"../../aws/organisations/accountGlobals.js";import{runDetectionPipeline as He}from"./detectionPipeline.js";import{assertCapacityPinsSupportedByAssembly as Ue,assertEngineCompatibleWithAssembly as _e}from"./engineCompat.js";import{STEP_IDS as _,STEP_NAMES as We}from"../../types/stepDefinitions.js";import{StepRegistry as Ke}from"../../steps/stepRegistry.js";import{checkTargetReadiness as qe}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as Ve}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as ze}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as me,runDockerBuild as Je}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as Ye,getStackNamesToDeploy as Qe,deployParallelPhase as Xe,deployStackSequential as Ze,runDockerPreCompute as et,runLambdaVersionPublish as tt,createBuildCallbacks as rt,willRunDockerBuild as nt}from"./applicationDeployHelpers.js";import{verifyAssemblyIntegrity as ye}from"./assemblyIntegrity.js";import{captureDatabaseConnectionOutputs as at,captureServiceTaskDefinitions as ot,reconcileDatabaseEndpointChange as it}from"./databaseEndpointReconcile.js";import{deployCodeOnly as st}from"./codeOnlyDeploy.js";import{checkDeployPathStackAvailability as ke}from"../activeDeploymentGuard.js";import{assertLeaseBeforeStack as W}from"./leaseGate.js";import{collectFullDeployArtefacts as ct}from"./artefactCollection.js";import{CfnRegistryService as lt}from"../../services/infrastructure/CfnRegistryService.js";import{ChangeSetProbe as dt}from"../../services/infrastructure/changeSetProbe.js";import{runApprovalGate as ut}from"./approvalGate.js";import{buildDestructionTicket as gt,runDestructionGate as ft}from"./destructionGate.js";import{DESTRUCTION_RESUME_TTL_MS as pt,signApprovalToken as mt}from"./plan/approvalToken.js";import{computeAssemblyDigest as yt}from"./plan/assemblyDigest.js";import{createMemoisedPlanLoader as kt}from"./plan/loadDeployPlan.js";import{runDriftPreFlightGate as ht}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as St}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as he}from"../remediation/postFailureRepairOffer.js";import{renderPinReportLines as bt,runPinRemediation as wt}from"../remediation/pinRemediation.js";import{degradedIdentityPinOutcomes as Se,renderIdentityPinReportLines as Tt}from"../remediation/migrateIdentity.js";import{runRecreatePreFlight as Et}from"../remediation/recreatePreFlight.js";import{runRecreateSurgery as Pt}from"../remediation/recreateResource.js";const j="applicationDeploy";function Nt(e){const n=Object.entries(e);if(n.length===0)return;const a={};for(const[t,i]of n){const w=Oe(t);a[w]=i}return a}D(Nt,"buildCdkImageTagParameters");function vt(e){if(e?.mode!==void 0&&e.mode!=="rollback"){if(e.imageTag)return u(new Error(`--image-tag requires rollback mode (explicit mode '${e.mode}' was supplied alongside an image tag)`));if(e.serviceImageTags)return u(new Error(`serviceImageTags requires rollback mode (explicit mode '${e.mode}' was supplied alongside serviceImageTags)`))}if(e?.mode==="rollback"){if(e.imageTag&&e.serviceImageTags)return u(new Error("rollback mode accepts exactly one of --image-tag or serviceImageTags \u2014 they are mutually exclusive"));if(!e.imageTag&&!e.serviceImageTags)return u(new Error("rollback mode requires --image-tag <tag> or serviceImageTags naming the image(s) to roll to"))}if(e?.serviceImageTags!==void 0){if(e.serviceName)return u(new Error("serviceImageTags already names its services \u2014 --service cannot be combined with it"));const n=Object.entries(e.serviceImageTags);if(n.length===0)return u(new Error("serviceImageTags must name at least one service"));for(const[a,t]of n){const i=Ie.safeParse(t);if(!i.success)return u(new Error(`serviceImageTags.${a} is not a valid image tag: ${i.error.message}`))}}return I(void 0)}D(vt,"validateRollbackOptions");function be(e,n,a){if(e.onStackTargets===void 0||a.length===0)return;const t=n.awsProvider.getAccountId(),i=n.awsProvider.getRegion();e.onStackTargets(a.map(w=>({stackName:w,...t!==""?{accountId:t}:{},...i!==""?{region:i}:{}})))}D(be,"emitStackTargets");async function yr(e,n,a){const{callbacks:t,options:i}=e,w=Date.now(),G=i?.mode??(i?.imageTag||i?.serviceImageTags?"rollback":i?.deployOnly?"code-only":"full");if(G==="restart"){const r=new Error('mode "restart" is not a deploy \u2014 call the restart() entry point (fjall rollout) instead');return t.onError?.(r),u(r)}const Q=vt(i);if(!Q.success){const r=S(Q.error.message),o=new Error(r);return t.onError?.(o),u(o)}const B=Me.buildDeploymentContext({deployType:"application",target:a.appName,path:a.path,region:n.awsProvider.getRegion(),callerIdentity:Le(n.awsProvider.getAccountId()),...$e({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc}),...e.managedDomainBindings!==void 0?{managedDomainBindings:e.managedDomainBindings}:{},...xe(e)},{verbose:i?.verbose,infraOnly:i?.infraOnly},e.orgConfig),P=n.frameworkRegistry.resolve({appPath:a.path});let X;const M=G==="rollback";if(P&&!M){X=P.builder.plan({appPath:a.path},P.detection);const r=rt(t),o=await P.builder.build(a.path,X,r,{skipBuild:i?.skipBuild,infraOnly:i?.infraOnly,...e.abortSignal!==void 0&&{abortSignal:e.abortSignal}});if(!o.success){const c=new Error(S(o.error.message));return t.onError?.(c),u(c)}}if(G==="code-only"||M){t.onLog?.(M?i?.serviceImageTags?`Rollback mode \u2014 rolling ${Object.keys(i.serviceImageTags).length} service(s) to their release-recorded image tags`:`Rollback mode \u2014 rolling to image tag ${i?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const r=ge(a.path,"cdk.out"),o=_e({cdkOutPath:r,onWarning:D(f=>t.onLog?.(f,"warn"),"onWarning")});if(!o.success){const f=new Error(S(o.error.message));return t.onError?.(f),u(f)}const c=Ue({cdkOutPath:r,capacityIdentityWire:B.capacityIdentity,appName:a.appName});if(!c.success){const f=new Error(S(c.error.message));return t.onError?.(f),u(f)}const m=Ae(r),l=m.declaresBuild,y=P?.detection.hasDockerfile===!0,g=l||y;F.debug(j,"Deploy-only branch entered",{mode:G,imageTag:i?.imageTag,serviceImageTags:i?.serviceImageTags,appName:a.appName,appPath:a.path,cdkOutPath:r,dockerProviderAvailable:e.dockerProvider!==void 0,builderName:P?.builder.name,hasDockerfileFromManifest:l,hasDockerfileFromDisk:y,hasDockerfile:g,manifestDockerServiceCount:m.ecsServices.length,manifestDockerPaths:m.ecsServices.map(f=>f.docker.path),manifestLambdaDockerEntryCount:m.lambdaEntries.length}),!l&&!y&&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");const s=await ke([O(a.appName,C.COMPUTE)],n.cfnService,e.abortSignal);if(!s.success)return t.onError?.(s.error),u(s.error);be(t,n,[O(a.appName,C.COMPUTE)]);let d={},k;if(!M&&e.dockerProvider!==void 0&&g){F.debug(j,"Running Docker build before code-only deploy",{source:l?"manifest":"disk"});const f=await Je(e,n,a,t);if(!f.success)return u(f.error);d=f.data.contentHashTagsByService,k=f.data}else F.debug(j,"Skipping Docker build",{reason:M?"rollback":e.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});const b=await W(e.assertLeaseForStack,O(a.appName,C.COMPUTE),e.abortSignal);return b!==void 0?(t.onError?.(b),u(b)):st(e,n,a,d,k)}t.onLog?.("Analysing infrastructure\u2026","info");const H=await He(a,n,B,t,e.abortSignal);if(!H.success){const r=new Error(S(H.error.message),{cause:H.error});return t.onError?.(r),u(r)}const p=H.data;try{await t.onDetectionComplete?.({...p,builderName:P?.builder.name??"unknown"})}catch(r){const o=new Error(S(Y(r)),{cause:r});return t.onError?.(o),u(o)}const we={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:i?.infraOnly??!1,hasDockerfile:p.hasDockerfile,pattern:p.pattern,resources:p.resources,...P&&{builderName:P.builder.name}},Z=Ke.getSteps(we),ee=Z.findIndex(r=>r.id===_.TARGET_READINESS),te=await ze(t,{stepId:_.TARGET_READINESS,stepName:We.TARGET_READINESS,...ee>=0&&{stepIndex:ee,totalSteps:Z.length}},async()=>{if(i?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const r=n.awsProvider.getAccountId(),o=e.orgConfig?.providerAccounts.find(y=>y.id===r)?.name,c=n.awsProvider.getCredentials(),m=Ve(e.orgConfig),l=await qe({cloudFormation:n.awsProvider.getClient(Re),ec2:new Ce({region:m,...c!==void 0&&{credentials:c}})},{id:r,...o!==void 0&&{name:o}},n.awsProvider.getRegion(),e.orgConfig,e.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(S(l.data.advisory))}:{kind:"error",error:new Error(S(l.error.message),{cause:l.error})}},e.abortSignal);if(!te.success)return te;const N=Be({pattern:p.pattern,resources:p.resources}),A=N.length,re=p.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(C.COMPUTE)&&i?.infraOnly!==!0;if(!p.hasDifferences&&!i?.force&&!re){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(i?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const r=p.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(C.COMPUTE);for(let c=0;c<N.length;c++){const m=N[c];r&&m===C.COMPUTE&&(t.onStepStart?.(_.DOCKER_OPERATIONS,me),t.onStepComplete?.(_.DOCKER_OPERATIONS,me,"skipped"));const l=pe(m,"deploy"),y=fe(m,"deploy");t.onStepStart?.(l,y,c,A),t.onStepComplete?.(l,y,"skipped",c,A)}const o=await n.stackService.resolveWebsiteUrl(a.appName);return I({target:a.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-w})}const L=ge(a.path,"cdk.out"),T=Qe({deployOrder:N,stackChanges:p.stackChanges,appName:a.appName,force:i?.force===!0,computeBuildPossible:re});be(t,n,[...T]);const K=await ke([...T],n.cfnService,e.abortSignal);if(!K.success)return t.onError?.(K.error),u(K.error);const ne={stacksToDeploy:[...T],imageBuild:nt({stackNamesToDeploy:T,appName:a.appName,hasDockerProvider:e.dockerProvider!==void 0,hasDockerfile:p.hasDockerfile})},ae=yt(p.currentHashes,ne),q=kt({cfnService:n.cfnService,cdkOutPath:L,changedStacks:[...T],assemblyDigest:ae,registry:new lt(n.awsProvider),changeSetProbe:new dt(n.awsProvider),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}),V=await ht(n.awsProvider,{stackNames:[...T],cdkOutPath:L,cfnService:n.cfnService,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(V.kind==="blocked"){const r=new Error(V.message);return t.onError?.(r),u(r)}const oe=V.verifiedAbsentByNecessity??[],U=e.approvalGate!==void 0||i?.approvalToken!==void 0&&i.approvalToken!==""||i?.planOnly===!0;if(U){const r=await ut({approvalGate:e.approvalGate,options:i??{},target:a.appName,cdkOutPath:L,detection:{stackChanges:p.stackChanges,currentHashes:p.currentHashes},scope:ne,cfnService:n.cfnService,loadPlan:q,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return r;const o=r.data;if(o.kind==="rejected")return t.onLog?.(`Deploy rejected: ${S(o.reason)}`,"warn"),I({target:a.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-w});if(o.kind==="awaiting"){const c=await q(),m=c.success?gt(c.data,oe):void 0;return I({target:a.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,...m!==void 0?{destructionTicket:m}:{},artefacts:[],durationMs:Date.now()-w})}}const v=await ft({loadPlan:q,options:i??{},target:a.appName,callbacks:t,verifiedAbsentByNecessity:oe,...e.destructionConsentGate!==void 0?{consentGate:e.destructionConsentGate}:{}});if(!v.success)return t.onError?.(v.error),u(v.error);if(v.data.kind==="withheld"){const r=U?mt({assemblyDigest:ae,ttlMs:pt}):void 0;return I({target:a.appName,deploymentType:"application",destructionPending:!0,destructionTicket:v.data.ticket,destructionOutcome:v.data.outcome,destructionHaltReasons:v.data.reasons,...r!==void 0?{destructionResumeToken:r.token,destructionResumeExpiresAt:r.expiresAt}:{},artefacts:[],durationMs:Date.now()-w})}if(v.data.kind==="proceed"){const{ticket:r,verbsByPhysicalName:o}=v.data,c=r.resources.filter(l=>o.get(l.physicalName)==="pin");if(c.length>0){const l=c.filter(d=>d.finding==="rename-detected"),y=c.filter(d=>d.finding!=="rename-detected");t.onLog?.(`Pin remediation for ${a.appName}: ${c.length} consented pin(s) \u2014 deploy stopped for config write-back; nothing was applied to AWS`,"warn");let g;if(l.length>0){if(e.identityPinRunner!==void 0)try{g=await e.identityPinRunner(l,e.abortSignal)}catch(d){g={outcomes:Se(l,"unknown",`identity-pin runner failed: ${S(Y(d))} \u2014 fix the cause, then run 'fjall migrate identity'`),verified:[],clean:!1}}else g={outcomes:Se(l,"unknown","this surface cannot run the identity-pin migration \u2014 run 'fjall migrate identity' to pin and validate this slot"),verified:[],clean:!1};for(const d of Tt(g))t.onLog?.(d,"warn")}let s;if(y.length>0){s=await wt(n.awsProvider,{targets:y.map(d=>({stack:d.stack,logicalId:d.logicalId,resourceType:d.resourceType,physicalName:d.physicalName,offendingProperties:d.offendingProperties})),...e.infrastructureSource!==void 0?{infrastructureContent:e.infrastructureSource.content,infrastructureFilePath:e.infrastructureSource.filePath}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});for(const d of bt(s))t.onLog?.(d,"warn")}return c.length<r.resources.length&&t.onLog?.("Remaining consented change(s) were not applied \u2014 the config write-back changes the plan, so they re-consent on the next deploy","warn"),I({target:a.appName,deploymentType:"application",pinPending:!0,...s!==void 0?{pinReport:s}:{},...g!==void 0?{identityPinReport:g}:{},pinDeferredConsents:r.resources.length-c.length,destructionTicket:r,artefacts:[],durationMs:Date.now()-w})}const m=r.resources.filter(l=>l.finding==="pinned-name-replacement"&&o.get(l.physicalName)==="recreate");if(m.length>0){const l=new Map;for(const s of m){const d=l.get(s.stack)??[];d.push(s),l.set(s.stack,d)}const y=await Et(n.awsProvider,{stacks:[...l].map(([s,d])=>({stackName:s,targets:d.map(k=>({logicalId:k.logicalId,resourceType:k.resourceType,physicalId:k.physicalName}))})),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!y.success){const s=new Error(S(y.error.message),{cause:y.error});return t.onError?.(s),u(s)}const g=[];for(const[s,d]of l){const k=await W(e.assertLeaseForStack,s,e.abortSignal,"destroy");if(k!==void 0)return g.length>0&&t.onLog?.(`Surgery already completed on ${g.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`,"warn"),t.onError?.(k),u(k);const b=d.map(h=>h.physicalName);t.onLog?.(`Recreate remediation for ${a.appName}: orchestrating the pinned-name replacement of ${b.join(", ")} on ${s}`,"warn");const f=await Pt(n.awsProvider,{stackName:s,targets:d.map(h=>({logicalId:h.logicalId,resourceType:h.resourceType,physicalId:h.physicalName})),appName:a.appName,consentedPhysicalNames:b,doneJournalPolicy:"archive-and-restart",plannedReplacementLogicalIds:d.map(h=>h.logicalId),onProgress:D(h=>t.onLog?.(h,"info"),"onProgress"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!f.success){const h=g.length>0?` Surgery already completed on ${g.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`:"",E=new Error(S(`${f.error.message}${h}`));return t.onError?.(E),u(E)}g.push(`${s} (${b.join(", ")})`)}}}const Te=e.orgConfig!==void 0&&!Ge(e.orgConfig.providerAccounts),Ee=je({account:e.orgConfig?.providerAccounts.find(r=>r.id===n.awsProvider.getAccountId()),estate:Te?"solo":"org"}),ie=await Fe(n,B,t,Ee);if(!ie.success)return ie;const se=O(a.appName,C.DATABASE),z=T.has(se),Pe=z?await at(n.cfnService,a.appName,e.abortSignal):void 0,Ne=z?await ot(n.ecsResolver,n.ecsService,a.appName,e.abortSignal):void 0,$={},x=new Map;let ce,le={};const R=D(async()=>{!z||!x.has(se)||await it({appName:a.appName,connectionOutputsBefore:Pe,serviceTaskDefinitionsBefore:Ne,services:{cfnService:n.cfnService,ecsService:n.ecsService,ecsResolver:n.ecsResolver},callbacks:t,...e.confirmDatabaseEndpointRestart!==void 0?{confirmRestart:e.confirmDatabaseEndpointRestart}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}})},"reconcileIfDatabaseReplaced");for(let r=0;r<N.length;r++){const o=N[r],c=O(a.appName,o),m=pe(o,"deploy"),l=fe(o,"deploy");if(!T.has(c)){t.onStepStart?.(m,l,r,A),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(m,l,"skipped",r,A);continue}const y=await W(e.assertLeaseForStack,c,e.abortSignal);if(y!==void 0)return await R(),t.onError?.(y),u(y);const g=Ye(N,r,a.appName,T);if(g.length>=2){const E=await ye({hashService:n.hashService,cdkOutPath:L,stackNames:g.map(J=>O(a.appName,J)),detectionHashes:p.currentHashes,approvalGated:U});if(!E.success)return await R(),t.onError?.(E.error),u(E.error);const ue=await Xe(g,a,n,B,t,r,A,p,$,x);if(!ue.success)return await R(),u(await he(n.awsProvider,{stackNames:g.map(J=>O(a.appName,J)),appName:a.appName,originalError:ue.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));r+=g.length-1;continue}const s=await et(o,e,n,a,t,p.hasDockerfile);if(s!==null&&!s.success)return await R(),u(s.error);s!==null&&s.success&&(ce=s.data);const d=s!==null&&s.success?s.data.contentHashTagsByService:{},k=Nt(d);if(s!==null){const E=await W(e.assertLeaseForStack,c,e.abortSignal);if(E!==void 0)return await R(),t.onError?.(E),u(E)}const b=await ye({hashService:n.hashService,cdkOutPath:L,stackNames:[c],detectionHashes:p.currentHashes,approvalGated:U});if(!b.success)return await R(),t.onError?.(b.error),u(b.error);const f=await Ze(o,n,B,t,r,A,$,k!==void 0||e.abortSignal!==void 0?{...k!==void 0?{parameters:k}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}:void 0);if(!f.success)return await R(),u(await he(n.awsProvider,{stackNames:[c],appName:a.appName,originalError:f.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));o===C.COMPUTE&&(le=await tt(a,n,t,d,e.abortSignal));const h=p.currentHashes.get(c);h&&x.set(c,h)}if(await R(),x.size>0){const r=await n.hashService.updateStateAfterDeploy(a.path,x);if(!r.success){const o=S(r.error.message);F.debug(j,"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 de=await n.stackService.resolveWebsiteUrl(a.appName);de&&($.websiteUrl=de);const ve=await ct(n,a.appName,ce,t,L,le,e.abortSignal);return await St(n.awsProvider,{stackNames:[...T],appName:a.appName,onLog:D((r,o)=>t.onLog?.(r,o),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}).catch(r=>{F.debug(j,"Forget completion sweep failed",{error:S(Y(r))})}),I({target:a.appName,deploymentType:"application",outputs:Object.keys($).length>0?$:void 0,artefacts:ve,durationMs:Date.now()-w})}D(yr,"deployApplication");export{yr as deployApplication,be as emitStackTargets};
1
+ var Re=Object.defineProperty;var D=(e,n)=>Re(e,"name",{value:n,configurable:!0});import{join as fe}from"path";import{CloudFormationClient as Ce}from"@aws-sdk/client-cloudformation";import{EC2Client as Ie}from"@aws-sdk/client-ec2";import{success as I,failure as d}from"@fjall/generator";import{logger as F}from"@fjall/util/logger";import{ImageTagSchema as Oe,getErrorMessage as Q,imageTagParameterName as Ae,maskSensitiveOutput as k}from"@fjall/util";import{parseDockerDeclarationsFromManifest as Le}from"@fjall/util/manifest";import{stubCallerIdentity as Be}from"../../types/deployment/index.js";import{getApplicationDeployOrder as Me,getApplicationStackName as O,getApplicationStepName as pe,getApplicationStepId as me,APPLICATION_STACKS as C}from"../../types/operations.js";import{CdkContextBuilder as $e}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as xe,buildCapacityIdentityContext as Fe,bootstrapOrFail as je}from"../contextHelpers.js";import{resolveDeployEnvironment as Ge}from"../deployEnvironment.js";import{buildBootstrapTagsIfResolved as He}from"../bootstrapTags.js";import{hasOrganisationTierAccount as Ue}from"../../aws/organisations/accountGlobals.js";import{runDetectionPipeline as _e}from"./detectionPipeline.js";import{assertCapacityPinsSupportedByAssembly as We,assertEngineCompatibleWithAssembly as Ke}from"./engineCompat.js";import{STEP_IDS as _,STEP_NAMES as qe}from"../../types/stepDefinitions.js";import{StepRegistry as Ve}from"../../steps/stepRegistry.js";import{checkTargetReadiness as ze}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as Je}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as Ye}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as ye,runDockerBuild as Qe}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as Xe,getStackNamesToDeploy as Ze,deployParallelPhase as et,deployStackSequential as tt,runDockerPreCompute as rt,runLambdaVersionPublish as nt,createBuildCallbacks as ot,willRunDockerBuild as at}from"./applicationDeployHelpers.js";import{verifyAssemblyIntegrity as ke}from"./assemblyIntegrity.js";import{captureDatabaseConnectionOutputs as it,captureServiceTaskDefinitions as st,reconcileDatabaseEndpointChange as ct}from"./databaseEndpointReconcile.js";import{deployCodeOnly as lt}from"./codeOnlyDeploy.js";import{checkDeployPathStackAvailability as he}from"../activeDeploymentGuard.js";import{assertLeaseBeforeStack as W}from"./leaseGate.js";import{collectFullDeployArtefacts as dt}from"./artefactCollection.js";import{CfnRegistryService as ut}from"../../services/infrastructure/CfnRegistryService.js";import{ChangeSetProbe as gt}from"../../services/infrastructure/changeSetProbe.js";import{runApprovalGate as ft}from"./approvalGate.js";import{buildDestructionTicket as pt,runDestructionGate as mt}from"./destructionGate.js";import{DESTRUCTION_RESUME_TTL_MS as yt,signApprovalToken as kt}from"./plan/approvalToken.js";import{computeAssemblyDigest as ht}from"./plan/assemblyDigest.js";import{createMemoisedPlanLoader as St}from"./plan/loadDeployPlan.js";import{runDriftPreFlightGate as bt}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as wt}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as Se}from"../remediation/postFailureRepairOffer.js";import{renderPinReportLines as Et,runPinRemediation as Tt}from"../remediation/pinRemediation.js";import{degradedIdentityPinOutcomes as be,renderIdentityPinReportLines as Pt}from"../remediation/migrateIdentity.js";import{runRecreatePreFlight as vt}from"../remediation/recreatePreFlight.js";import{runRecreateSurgery as Nt}from"../remediation/recreateResource.js";const j="applicationDeploy";function Dt(e){const n=Object.entries(e);if(n.length===0)return;const o={};for(const[t,i]of n){const w=Ae(t);o[w]=i}return o}D(Dt,"buildCdkImageTagParameters");function Rt(e){if(e?.mode!==void 0&&e.mode!=="rollback"){if(e.imageTag)return d(new Error(`--image-tag requires rollback mode (explicit mode '${e.mode}' was supplied alongside an image tag)`));if(e.serviceImageTags)return d(new Error(`serviceImageTags requires rollback mode (explicit mode '${e.mode}' was supplied alongside serviceImageTags)`))}if(e?.mode==="rollback"){if(e.imageTag&&e.serviceImageTags)return d(new Error("rollback mode accepts exactly one of --image-tag or serviceImageTags \u2014 they are mutually exclusive"));if(!e.imageTag&&!e.serviceImageTags)return d(new Error("rollback mode requires --image-tag <tag> or serviceImageTags naming the image(s) to roll to"))}if(e?.serviceImageTags!==void 0){if(e.serviceName)return d(new Error("serviceImageTags already names its services \u2014 --service cannot be combined with it"));const n=Object.entries(e.serviceImageTags);if(n.length===0)return d(new Error("serviceImageTags must name at least one service"));for(const[o,t]of n){const i=Oe.safeParse(t);if(!i.success)return d(new Error(`serviceImageTags.${o} is not a valid image tag: ${i.error.message}`))}}return I(void 0)}D(Rt,"validateRollbackOptions");function we(e,n,o){if(e.onStackTargets===void 0||o.length===0)return;const t=n.awsProvider.getAccountId(),i=n.awsProvider.getRegion();e.onStackTargets(o.map(w=>({stackName:w,...t!==""?{accountId:t}:{},...i!==""?{region:i}:{}})))}D(we,"emitStackTargets");async function Sr(e,n,o){const{callbacks:t,options:i}=e,w=Date.now(),G=i?.mode??(i?.imageTag||i?.serviceImageTags?"rollback":i?.deployOnly?"code-only":"full");if(G==="restart"){const r=new Error('mode "restart" is not a deploy \u2014 call the restart() entry point (fjall rollout) instead');return t.onError?.(r),d(r)}const X=Rt(i);if(!X.success){const r=k(X.error.message),a=new Error(r);return t.onError?.(a),d(a)}const K=Ge({orgConfig:e.orgConfig,accountId:n.awsProvider.getAccountId()});if(!K.success){const r=new Error(k(K.error.message));return t.onError?.(r),d(r)}const B=$e.buildDeploymentContext({deployType:"application",target:o.appName,path:o.path,region:n.awsProvider.getRegion(),environment:K.data,callerIdentity:Be(n.awsProvider.getAccountId()),...xe({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc}),...e.managedDomainBindings!==void 0?{managedDomainBindings:e.managedDomainBindings}:{},...Fe(e)},{verbose:i?.verbose,infraOnly:i?.infraOnly},e.orgConfig),P=n.frameworkRegistry.resolve({appPath:o.path});let Z;const M=G==="rollback";if(P&&!M){Z=P.builder.plan({appPath:o.path},P.detection);const r=ot(t),a=await P.builder.build(o.path,Z,r,{skipBuild:i?.skipBuild,infraOnly:i?.infraOnly,...e.abortSignal!==void 0&&{abortSignal:e.abortSignal}});if(!a.success){const c=new Error(k(a.error.message));return t.onError?.(c),d(c)}}if(G==="code-only"||M){t.onLog?.(M?i?.serviceImageTags?`Rollback mode \u2014 rolling ${Object.keys(i.serviceImageTags).length} service(s) to their release-recorded image tags`:`Rollback mode \u2014 rolling to image tag ${i?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const r=fe(o.path,"cdk.out"),a=Ke({cdkOutPath:r,onWarning:D(f=>t.onLog?.(f,"warn"),"onWarning")});if(!a.success){const f=new Error(k(a.error.message));return t.onError?.(f),d(f)}const c=We({cdkOutPath:r,capacityIdentityWire:B.capacityIdentity,appName:o.appName});if(!c.success){const f=new Error(k(c.error.message));return t.onError?.(f),d(f)}const m=Le(r),l=m.declaresBuild,y=P?.detection.hasDockerfile===!0,g=l||y;F.debug(j,"Deploy-only branch entered",{mode:G,imageTag:i?.imageTag,serviceImageTags:i?.serviceImageTags,appName:o.appName,appPath:o.path,cdkOutPath:r,dockerProviderAvailable:e.dockerProvider!==void 0,builderName:P?.builder.name,hasDockerfileFromManifest:l,hasDockerfileFromDisk:y,hasDockerfile:g,manifestDockerServiceCount:m.ecsServices.length,manifestDockerPaths:m.ecsServices.map(f=>f.docker.path),manifestLambdaDockerEntryCount:m.lambdaEntries.length}),!l&&!y&&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");const s=await he([O(o.appName,C.COMPUTE)],n.cfnService,e.abortSignal);if(!s.success)return t.onError?.(s.error),d(s.error);we(t,n,[O(o.appName,C.COMPUTE)]);let u={},h;if(!M&&e.dockerProvider!==void 0&&g){F.debug(j,"Running Docker build before code-only deploy",{source:l?"manifest":"disk"});const f=await Qe(e,n,o,t);if(!f.success)return d(f.error);u=f.data.contentHashTagsByService,h=f.data}else F.debug(j,"Skipping Docker build",{reason:M?"rollback":e.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});const b=await W(e.assertLeaseForStack,O(o.appName,C.COMPUTE),e.abortSignal);return b!==void 0?(t.onError?.(b),d(b)):lt(e,n,o,u,h)}t.onLog?.("Analysing infrastructure\u2026","info");const H=await _e(o,n,B,t,e.abortSignal);if(!H.success){const r=new Error(k(H.error.message),{cause:H.error});return t.onError?.(r),d(r)}const p=H.data;try{await t.onDetectionComplete?.({...p,builderName:P?.builder.name??"unknown"})}catch(r){const a=new Error(k(Q(r)),{cause:r});return t.onError?.(a),d(a)}const Ee={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:i?.infraOnly??!1,hasDockerfile:p.hasDockerfile,pattern:p.pattern,resources:p.resources,...P&&{builderName:P.builder.name}},ee=Ve.getSteps(Ee),te=ee.findIndex(r=>r.id===_.TARGET_READINESS),re=await Ye(t,{stepId:_.TARGET_READINESS,stepName:qe.TARGET_READINESS,...te>=0&&{stepIndex:te,totalSteps:ee.length}},async()=>{if(i?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const r=n.awsProvider.getAccountId(),a=e.orgConfig?.providerAccounts.find(y=>y.id===r)?.name,c=n.awsProvider.getCredentials(),m=Je(e.orgConfig),l=await ze({cloudFormation:n.awsProvider.getClient(Ce),ec2:new Ie({region:m,...c!==void 0&&{credentials:c}})},{id:r,...a!==void 0&&{name:a}},n.awsProvider.getRegion(),e.orgConfig,e.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(k(l.data.advisory))}:{kind:"error",error:new Error(k(l.error.message),{cause:l.error})}},e.abortSignal);if(!re.success)return re;const v=Me({pattern:p.pattern,resources:p.resources}),A=v.length,ne=p.hasDockerfile&&e.dockerProvider!==void 0&&v.includes(C.COMPUTE)&&i?.infraOnly!==!0;if(!p.hasDifferences&&!i?.force&&!ne){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(i?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const r=p.hasDockerfile&&e.dockerProvider!==void 0&&v.includes(C.COMPUTE);for(let c=0;c<v.length;c++){const m=v[c];r&&m===C.COMPUTE&&(t.onStepStart?.(_.DOCKER_OPERATIONS,ye),t.onStepComplete?.(_.DOCKER_OPERATIONS,ye,"skipped"));const l=me(m,"deploy"),y=pe(m,"deploy");t.onStepStart?.(l,y,c,A),t.onStepComplete?.(l,y,"skipped",c,A)}const a=await n.stackService.resolveWebsiteUrl(o.appName);return I({target:o.appName,deploymentType:"application",outputs:a?{websiteUrl:a}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-w})}const L=fe(o.path,"cdk.out"),E=Ze({deployOrder:v,stackChanges:p.stackChanges,appName:o.appName,force:i?.force===!0,computeBuildPossible:ne});we(t,n,[...E]);const q=await he([...E],n.cfnService,e.abortSignal);if(!q.success)return t.onError?.(q.error),d(q.error);const oe={stacksToDeploy:[...E],imageBuild:at({stackNamesToDeploy:E,appName:o.appName,hasDockerProvider:e.dockerProvider!==void 0,hasDockerfile:p.hasDockerfile})},ae=ht(p.currentHashes,oe),V=St({cfnService:n.cfnService,cdkOutPath:L,changedStacks:[...E],assemblyDigest:ae,registry:new ut(n.awsProvider),changeSetProbe:new gt(n.awsProvider),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}),z=await bt(n.awsProvider,{stackNames:[...E],cdkOutPath:L,cfnService:n.cfnService,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(z.kind==="blocked"){const r=new Error(z.message);return t.onError?.(r),d(r)}const ie=z.verifiedAbsentByNecessity??[],U=e.approvalGate!==void 0||i?.approvalToken!==void 0&&i.approvalToken!==""||i?.planOnly===!0;if(U){const r=await ft({approvalGate:e.approvalGate,options:i??{},target:o.appName,cdkOutPath:L,detection:{stackChanges:p.stackChanges,currentHashes:p.currentHashes},scope:oe,cfnService:n.cfnService,loadPlan:V,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return r;const a=r.data;if(a.kind==="rejected")return t.onLog?.(`Deploy rejected: ${k(a.reason)}`,"warn"),I({target:o.appName,deploymentType:"application",rejected:!0,artefacts:[],...a.planDetail!==void 0?{planDetail:a.planDetail}:{},durationMs:Date.now()-w});if(a.kind==="awaiting"){const c=await V(),m=c.success?pt(c.data,ie):void 0;return I({target:o.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:a.approvalToken,assemblyDigest:a.assemblyDigest,planDetail:a.planDetail,...m!==void 0?{destructionTicket:m}:{},artefacts:[],durationMs:Date.now()-w})}}const N=await mt({loadPlan:V,options:i??{},target:o.appName,callbacks:t,verifiedAbsentByNecessity:ie,...e.destructionConsentGate!==void 0?{consentGate:e.destructionConsentGate}:{}});if(!N.success)return t.onError?.(N.error),d(N.error);if(N.data.kind==="withheld"){const r=U?kt({assemblyDigest:ae,ttlMs:yt}):void 0;return I({target:o.appName,deploymentType:"application",destructionPending:!0,destructionTicket:N.data.ticket,destructionOutcome:N.data.outcome,destructionHaltReasons:N.data.reasons,...r!==void 0?{destructionResumeToken:r.token,destructionResumeExpiresAt:r.expiresAt}:{},artefacts:[],durationMs:Date.now()-w})}if(N.data.kind==="proceed"){const{ticket:r,verbsByPhysicalName:a}=N.data,c=r.resources.filter(l=>a.get(l.physicalName)==="pin");if(c.length>0){const l=c.filter(u=>u.finding==="rename-detected"),y=c.filter(u=>u.finding!=="rename-detected");t.onLog?.(`Pin remediation for ${o.appName}: ${c.length} consented pin(s) \u2014 deploy stopped for config write-back; nothing was applied to AWS`,"warn");let g;if(l.length>0){if(e.identityPinRunner!==void 0)try{g=await e.identityPinRunner(l,e.abortSignal)}catch(u){g={outcomes:be(l,"unknown",`identity-pin runner failed: ${k(Q(u))} \u2014 fix the cause, then run 'fjall migrate identity'`),verified:[],clean:!1}}else g={outcomes:be(l,"unknown","this surface cannot run the identity-pin migration \u2014 run 'fjall migrate identity' to pin and validate this slot"),verified:[],clean:!1};for(const u of Pt(g))t.onLog?.(u,"warn")}let s;if(y.length>0){s=await Tt(n.awsProvider,{targets:y.map(u=>({stack:u.stack,logicalId:u.logicalId,resourceType:u.resourceType,physicalName:u.physicalName,offendingProperties:u.offendingProperties})),...e.infrastructureSource!==void 0?{infrastructureContent:e.infrastructureSource.content,infrastructureFilePath:e.infrastructureSource.filePath}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});for(const u of Et(s))t.onLog?.(u,"warn")}return c.length<r.resources.length&&t.onLog?.("Remaining consented change(s) were not applied \u2014 the config write-back changes the plan, so they re-consent on the next deploy","warn"),I({target:o.appName,deploymentType:"application",pinPending:!0,...s!==void 0?{pinReport:s}:{},...g!==void 0?{identityPinReport:g}:{},pinDeferredConsents:r.resources.length-c.length,destructionTicket:r,artefacts:[],durationMs:Date.now()-w})}const m=r.resources.filter(l=>l.finding==="pinned-name-replacement"&&a.get(l.physicalName)==="recreate");if(m.length>0){const l=new Map;for(const s of m){const u=l.get(s.stack)??[];u.push(s),l.set(s.stack,u)}const y=await vt(n.awsProvider,{stacks:[...l].map(([s,u])=>({stackName:s,targets:u.map(h=>({logicalId:h.logicalId,resourceType:h.resourceType,physicalId:h.physicalName}))})),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!y.success){const s=new Error(k(y.error.message),{cause:y.error});return t.onError?.(s),d(s)}const g=[];for(const[s,u]of l){const h=await W(e.assertLeaseForStack,s,e.abortSignal,"destroy");if(h!==void 0)return g.length>0&&t.onLog?.(`Surgery already completed on ${g.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`,"warn"),t.onError?.(h),d(h);const b=u.map(S=>S.physicalName);t.onLog?.(`Recreate remediation for ${o.appName}: orchestrating the pinned-name replacement of ${b.join(", ")} on ${s}`,"warn");const f=await Nt(n.awsProvider,{stackName:s,targets:u.map(S=>({logicalId:S.logicalId,resourceType:S.resourceType,physicalId:S.physicalName})),appName:o.appName,consentedPhysicalNames:b,doneJournalPolicy:"archive-and-restart",plannedReplacementLogicalIds:u.map(S=>S.logicalId),onProgress:D(S=>t.onLog?.(S,"info"),"onProgress"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!f.success){const S=g.length>0?` Surgery already completed on ${g.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`:"",T=new Error(k(`${f.error.message}${S}`));return t.onError?.(T),d(T)}g.push(`${s} (${b.join(", ")})`)}}}const Te=e.orgConfig!==void 0&&!Ue(e.orgConfig.providerAccounts),Pe=He({account:e.orgConfig?.providerAccounts.find(r=>r.id===n.awsProvider.getAccountId()),estate:Te?"solo":"org"}),se=await je(n,B,t,Pe);if(!se.success)return se;const ce=O(o.appName,C.DATABASE),J=E.has(ce),ve=J?await it(n.cfnService,o.appName,e.abortSignal):void 0,Ne=J?await st(n.ecsResolver,n.ecsService,o.appName,e.abortSignal):void 0,$={},x=new Map;let le,de={};const R=D(async()=>{!J||!x.has(ce)||await ct({appName:o.appName,connectionOutputsBefore:ve,serviceTaskDefinitionsBefore:Ne,services:{cfnService:n.cfnService,ecsService:n.ecsService,ecsResolver:n.ecsResolver},callbacks:t,...e.confirmDatabaseEndpointRestart!==void 0?{confirmRestart:e.confirmDatabaseEndpointRestart}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}})},"reconcileIfDatabaseReplaced");for(let r=0;r<v.length;r++){const a=v[r],c=O(o.appName,a),m=me(a,"deploy"),l=pe(a,"deploy");if(!E.has(c)){t.onStepStart?.(m,l,r,A),t.onLog?.(`Skipping ${a} \u2014 no changes detected`,"info"),t.onStepComplete?.(m,l,"skipped",r,A);continue}const y=await W(e.assertLeaseForStack,c,e.abortSignal);if(y!==void 0)return await R(),t.onError?.(y),d(y);const g=Xe(v,r,o.appName,E);if(g.length>=2){const T=await ke({hashService:n.hashService,cdkOutPath:L,stackNames:g.map(Y=>O(o.appName,Y)),detectionHashes:p.currentHashes,approvalGated:U});if(!T.success)return await R(),t.onError?.(T.error),d(T.error);const ge=await et(g,o,n,B,t,r,A,p,$,x);if(!ge.success)return await R(),d(await Se(n.awsProvider,{stackNames:g.map(Y=>O(o.appName,Y)),appName:o.appName,originalError:ge.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));r+=g.length-1;continue}const s=await rt(a,e,n,o,t,p.hasDockerfile);if(s!==null&&!s.success)return await R(),d(s.error);s!==null&&s.success&&(le=s.data);const u=s!==null&&s.success?s.data.contentHashTagsByService:{},h=Dt(u);if(s!==null){const T=await W(e.assertLeaseForStack,c,e.abortSignal);if(T!==void 0)return await R(),t.onError?.(T),d(T)}const b=await ke({hashService:n.hashService,cdkOutPath:L,stackNames:[c],detectionHashes:p.currentHashes,approvalGated:U});if(!b.success)return await R(),t.onError?.(b.error),d(b.error);const f=await tt(a,n,B,t,r,A,$,h!==void 0||e.abortSignal!==void 0?{...h!==void 0?{parameters:h}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}:void 0);if(!f.success)return await R(),d(await Se(n.awsProvider,{stackNames:[c],appName:o.appName,originalError:f.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));a===C.COMPUTE&&(de=await nt(o,n,t,u,e.abortSignal));const S=p.currentHashes.get(c);S&&x.set(c,S)}if(await R(),x.size>0){const r=await n.hashService.updateStateAfterDeploy(o.path,x);if(!r.success){const a=k(r.error.message);F.debug(j,"Failed to update state file",{error:a}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${a}`,"warn")}}const ue=await n.stackService.resolveWebsiteUrl(o.appName);ue&&($.websiteUrl=ue);const De=await dt(n,o.appName,le,t,L,de,e.abortSignal);return await wt(n.awsProvider,{stackNames:[...E],appName:o.appName,onLog:D((r,a)=>t.onLog?.(r,a),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}).catch(r=>{F.debug(j,"Forget completion sweep failed",{error:k(Q(r))})}),I({target:o.appName,deploymentType:"application",outputs:Object.keys($).length>0?$:void 0,artefacts:De,durationMs:Date.now()-w})}D(Sr,"deployApplication");export{Sr as deployApplication,we as emitStackTargets};
@@ -1 +1 @@
1
- var D=Object.defineProperty;var s=(r,a)=>D(r,"name",{value:a,configurable:!0});import{success as F,failure as P}from"@fjall/generator";import{getErrorMessage as k}from"@fjall/util";import{logger as C}from"@fjall/util/logger";import{getApplicationDestroyOrder as L,getApplicationStackName as R,getApplicationStepName as w,getApplicationStepId as b}from"../../types/operations.js";import{emitStackTargets as A}from"./applicationDeploy.js";import{runDestroyPreFlight as I}from"./destroyPreFlight.js";import{stubCallerIdentity as O}from"../../types/deployment/index.js";import{deriveResourcesFromManifestStacks as $}from"../../types/detection/patternDetection.js";import{CdkContextBuilder as v}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as E}from"../contextHelpers.js";import{deleteStateFile as M,readStateFile as j}from"../../types/config/FjallState.js";async function Y(r,a,n){const{callbacks:t}=r,N=Date.now(),u=a.frameworkRegistry.resolve({appPath:n.path})?.detection.pattern??null;let g;try{const e=await j(n.path);if(e!==null){const o=Object.keys(e.templateHashes);o.length>0&&(g=$(o))}}catch(e){C.debug("applicationDestroy","Could not read state file for resource detection",{error:k(e)}),t.onLog?.("Could not read state file for resource detection \u2014 falling back to pattern detection","warn")}const x=v.buildDeploymentContext({deployType:"application",target:n.appName,path:n.path,region:a.awsProvider.getRegion(),callerIdentity:O(a.awsProvider.getAccountId()),...E({orgConfig:r.orgConfig,identity:r.identity})},{verbose:r.options?.verbose},r.orgConfig),d=L({pattern:u,resources:g}),i=d.length;t.onLog?.(`Destroying ${n.appName} (${i} stacks, ${u??"standard"} pattern)`,"info");const S=d.map(e=>R(n.appName,e));A(t,a,S);const m=await I(a.awsProvider,{stackNames:S,abortSignal:r.abortSignal});if(!m.success)return t.onError?.(m.error),P(m.error);const h=[],y=[],f=await a.stackService.destroyAllStacks(x,{onOutput:s(e=>{t.onOutput?.(e)},"onOutput"),onLog:t.onLog,onStackCleanupProgress:t.onStackCleanupProgress,onResourceProgress:s((e,o)=>{t.onResourceProgress?.(e),o&&t.onParallelStackResourceProgress?.(o,e)},"onResourceProgress"),onStackStart:s((e,o)=>{const l=b(e,"destroy"),c=w(e,"destroy"),p=d.indexOf(e);t.onStepStart?.(l,c,p,i)},"onStackStart"),onStackComplete:s(async(e,o)=>{const l=b(e,"destroy"),c=w(e,"destroy"),p=d.indexOf(e);o.success?o.data?.skipped?(y.push(o.data.stackName||e),t.onStepComplete?.(l,c,"skipped",p,i)):(h.push(o.data?.stackName||e),t.onStepComplete?.(l,c,"completed",p,i)):(t.onStepComplete?.(l,c,"error",p,i),t.onError?.(o.error))},"onStackComplete"),onParallelPhaseStart:s((e,o)=>{t.onLog?.(`Parallel phase: ${o}`,"info"),t.onParallelPhaseStart?.(e,o)},"onParallelPhaseStart"),onParallelPhaseComplete:s(e=>{const o=e.filter(l=>!l.success);o.length>0&&t.onLog?.(`Parallel phase completed with ${o.length} failure(s)`,"warn"),t.onParallelPhaseComplete?.(e)},"onParallelPhaseComplete")},g,r.abortSignal,r.assertLeaseForStack);if(!f.success)return t.onError?.(f.error),P(f.error);try{await M(n.path)}catch(e){C.debug("applicationDestroy","Failed to delete state file (non-critical)",{error:k(e)}),t.onLog?.("Failed to delete state file (non-critical)","warn")}return F({target:n.appName,deploymentType:"application",stacksDestroyed:h,skippedStacks:y,durationMs:Date.now()-N})}s(Y,"destroyApplication");export{Y as destroyApplication};
1
+ var F=Object.defineProperty;var s=(r,a)=>F(r,"name",{value:a,configurable:!0});import{success as I,failure as k}from"@fjall/generator";import{getErrorMessage as C}from"@fjall/util";import{logger as w}from"@fjall/util/logger";import{getApplicationDestroyOrder as R,getApplicationStackName as v,getApplicationStepName as b,getApplicationStepId as N}from"../../types/operations.js";import{emitStackTargets as A}from"./applicationDeploy.js";import{runDestroyPreFlight as L}from"./destroyPreFlight.js";import{stubCallerIdentity as O}from"../../types/deployment/index.js";import{deriveResourcesFromManifestStacks as E}from"../../types/detection/patternDetection.js";import{CdkContextBuilder as $}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as M}from"../contextHelpers.js";import{resolveDeployEnvironment as j}from"../deployEnvironment.js";import{deleteStateFile as B,readStateFile as H}from"../../types/config/FjallState.js";async function ee(r,a,n){const{callbacks:t}=r,D=Date.now(),u=a.frameworkRegistry.resolve({appPath:n.path})?.detection.pattern??null;let g;try{const e=await H(n.path);if(e!==null){const o=Object.keys(e.templateHashes);o.length>0&&(g=E(o))}}catch(e){w.debug("applicationDestroy","Could not read state file for resource detection",{error:C(e)}),t.onLog?.("Could not read state file for resource detection \u2014 falling back to pattern detection","warn")}const S=j({orgConfig:r.orgConfig,accountId:a.awsProvider.getAccountId()}),x=$.buildDeploymentContext({deployType:"application",target:n.appName,path:n.path,region:a.awsProvider.getRegion(),...S.success?{environment:S.data}:{},callerIdentity:O(a.awsProvider.getAccountId()),...M({orgConfig:r.orgConfig,identity:r.identity})},{verbose:r.options?.verbose},r.orgConfig),d=R({pattern:u,resources:g}),i=d.length;t.onLog?.(`Destroying ${n.appName} (${i} stacks, ${u??"standard"} pattern)`,"info");const y=d.map(e=>v(n.appName,e));A(t,a,y);const m=await L(a.awsProvider,{stackNames:y,abortSignal:r.abortSignal});if(!m.success)return t.onError?.(m.error),k(m.error);const h=[],P=[],f=await a.stackService.destroyAllStacks(x,{onOutput:s(e=>{t.onOutput?.(e)},"onOutput"),onLog:t.onLog,onStackCleanupProgress:t.onStackCleanupProgress,onResourceProgress:s((e,o)=>{t.onResourceProgress?.(e),o&&t.onParallelStackResourceProgress?.(o,e)},"onResourceProgress"),onStackStart:s((e,o)=>{const l=N(e,"destroy"),c=b(e,"destroy"),p=d.indexOf(e);t.onStepStart?.(l,c,p,i)},"onStackStart"),onStackComplete:s(async(e,o)=>{const l=N(e,"destroy"),c=b(e,"destroy"),p=d.indexOf(e);o.success?o.data?.skipped?(P.push(o.data.stackName||e),t.onStepComplete?.(l,c,"skipped",p,i)):(h.push(o.data?.stackName||e),t.onStepComplete?.(l,c,"completed",p,i)):(t.onStepComplete?.(l,c,"error",p,i),t.onError?.(o.error))},"onStackComplete"),onParallelPhaseStart:s((e,o)=>{t.onLog?.(`Parallel phase: ${o}`,"info"),t.onParallelPhaseStart?.(e,o)},"onParallelPhaseStart"),onParallelPhaseComplete:s(e=>{const o=e.filter(l=>!l.success);o.length>0&&t.onLog?.(`Parallel phase completed with ${o.length} failure(s)`,"warn"),t.onParallelPhaseComplete?.(e)},"onParallelPhaseComplete")},g,r.abortSignal,r.assertLeaseForStack);if(!f.success)return t.onError?.(f.error),k(f.error);try{await B(n.path)}catch(e){w.debug("applicationDestroy","Failed to delete state file (non-critical)",{error:C(e)}),t.onLog?.("Failed to delete state file (non-critical)","warn")}return I({target:n.appName,deploymentType:"application",stacksDestroyed:h,skippedStacks:P,durationMs:Date.now()-D})}s(ee,"destroyApplication");export{ee as destroyApplication};
@@ -0,0 +1,33 @@
1
+ import { type Result } from "@fjall/generator";
2
+ import type { OrgConfig } from "../types/config/orgConfig.js";
3
+ /**
4
+ * Why the deployment environment could not be resolved. Callers surface
5
+ * `message` verbatim; the discriminant exists so a caller can tell a
6
+ * transport failure (`no-org-config` — retry may help) from a configuration
7
+ * failure (the other three — retrying will not).
8
+ */
9
+ export type DeployEnvironmentFailureKind = "no-org-config" | "no-accounts" | "account-not-in-config" | "account-has-no-stage";
10
+ export declare class DeployEnvironmentError extends Error {
11
+ readonly kind: DeployEnvironmentFailureKind;
12
+ constructor(kind: DeployEnvironmentFailureKind, message: string);
13
+ }
14
+ /**
15
+ * Resolve the environment a deploy is targeting, from the account it is
16
+ * targeting and the org config that describes it.
17
+ *
18
+ * This is the PRE-SYNTH half of a resolution that
19
+ * `components/infrastructure/lib/utils/env.ts` also performs DURING synth.
20
+ * Both route through the same `resolveSynthEnvironment` so they cannot
21
+ * disagree — but the synth-time half degrades to a warn-and-DESTROY sentinel
22
+ * when its inputs are missing, which is the wrong default for a real deploy
23
+ * against a real account. Resolving here lets the deploy refuse instead, and
24
+ * lets the caller pass the answer down explicitly so no inference runs at all.
25
+ *
26
+ * Deliberately a `Result`, not a throw: every caller is already inside a
27
+ * `Result`-returning orchestrator.
28
+ */
29
+ export declare function resolveDeployEnvironment(params: {
30
+ orgConfig?: OrgConfig;
31
+ accountId?: string;
32
+ accountName?: string;
33
+ }): Result<string, DeployEnvironmentError>;
@@ -0,0 +1 @@
1
+ var f=Object.defineProperty;var u=(o,n)=>f(o,"name",{value:n,configurable:!0});import{success as v,failure as s}from"@fjall/generator";import{resolveSynthEnvironment as g}from"@fjall/util";class c extends Error{static{u(this,"DeployEnvironmentError")}kind;constructor(n,e){super(e),this.kind=n,this.name="DeployEnvironmentError"}}const l="The environment decides every removal policy in the synthesised template \u2014 production retains data on stack deletion, every other stage destroys it. Deploying without it would silently pick DESTROY.";function $(o){const{orgConfig:n,accountId:e,accountName:i}=o;if(n===void 0)return s(new c("no-org-config",`Cannot resolve the deployment environment: no organisation configuration was available. ${l} This usually means the Fjall API was unreachable or the deploy's credentials no longer authorise reading it. Retry once the API is reachable.`));const r=n.providerAccounts;if(r.length===0)return s(new c("no-accounts",`Cannot resolve the deployment environment: the organisation configuration lists no AWS accounts. ${l} Connect an account (\`fjall connect\`) and retry.`));const a=(e!==void 0&&e!==""?r.find(t=>t.id===e):void 0)??(i!==void 0&&i!==""?r.find(t=>t.name===i):void 0);if(a===void 0){const t=e??i??"<unidentified>",m=r.map(h=>`${h.name} (${h.id})`).join(", ");return s(new c("account-not-in-config",`Cannot resolve the deployment environment: account ${t} is not in the organisation configuration. ${l} Known accounts: ${m}. Run \`fjall target list\` to see the targets this organisation can deploy to.`))}const d=g(a);return d===void 0?s(new c("account-has-no-stage",`Cannot resolve the deployment environment: account ${a.name} (${a.id}) has no environment stage set. ${l} Set the account's environment in the Fjall console, then retry.`)):v(d)}u($,"resolveDeployEnvironment");export{c as DeployEnvironmentError,$ as resolveDeployEnvironment};
@@ -1 +1 @@
1
- var _=Object.defineProperty;var C=(t,n)=>_(t,"name",{value:n,configurable:!0});import{join as x}from"path";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as i,getErrorMessage as k}from"@fjall/util";import{stubCallerIdentity as L}from"../../types/deployment/index.js";import{CloudFormationClient as T,DescribeStacksCommand as M}from"@aws-sdk/client-cloudformation";import{S3Client as A}from"@aws-sdk/client-s3";import{BackupClient as K}from"@aws-sdk/client-backup";import{IAMClient as V}from"@aws-sdk/client-iam";import{accountHasDisasterRecovery as j,describeSurvivingBackupVault as U,formatSurvivingVaultWarning as G}from"../../aws/organisations/backup.js";import{cleanupOrphanedOidcProvider as H}from"../../aws/organisations/oidcProvider.js";import{composeSdkAbortSignal as W}from"../../aws/organisations/types.js";import{assertLeaseBeforeStack as Y}from"../application/leaseGate.js";import{ORGANISATION_TYPES as b,getOrganisationStackName as q}from"../../types/operations.js";import{CdkContextBuilder as z}from"../../services/supporting/CdkContextBuilder.js";import{regionSuffix as J}from"../../types/config/FjallState.js";import{assertAssemblyEngineCompat as Q,buildParamsContext as X,assumeCascadeRole as Z,forwardOutput as B}from"../contextHelpers.js";import{cascadeOperationKey as ee}from"./cascadeHelpers.js";import{capturedSweepBuckets as te,cleanupFailedStack as re,preEmptyStackBuckets as ae,sweepOrphanedDestroyBuckets as I}from"../stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as oe,STACK_FAILED_STATE_PATTERN as ne}from"../../types/constants.js";async function Ne(t,n,d,e,r,a,o,y){const g=Date.now(),m=ee(e.name,a),f=C((s,p=s)=>(o.onCascadeAccountComplete?.(m,!1,p,a),{accountName:e.name,accountId:e.id,region:a,success:!1,duration:Date.now()-g,error:s}),"fail");o.onCascadeAccountStart?.(m,e.id,a,r);const v=await Z(n.awsProvider,e.id,a,`fjall-cascade-destroy-${e.name}`);if(!v.success){const s=i(v.error.message);return f(`AssumeRole failed: ${s}`,s)}const{provider:u,credentials:D}=v.data,h=x(d.path,`cdk.out.${e.id}.${J(a)}`),R=z.buildDeploymentContext({deployType:r,target:d.target,path:d.path,assemblyDir:h,region:a,accountName:e.name,callerIdentity:L(e.id),...X({orgConfig:t.orgConfig,identity:t.identity,region:a,primaryRegion:y?.primaryRegion})},{verbose:t.options?.verbose},t.orgConfig);o.onCascadeAccountPhaseChange?.(m,"synth",a);const E=await n.cdkService.runCdkSynth(R,B(o));if(!E.success)return f(i(`Synth failed: ${E.error}`));const P=Q(h,o);if(!P.success)return f(P.error.message);o.onCascadeAccountPhaseChange?.(m,"destroy",a);const c=q(r==="platform"?b.PLATFORM:b.ACCOUNT),w=await Y(t.assertLeaseForStack,c,t.abortSignal,"destroy");if(w!==void 0)return{...f(w.message),leaseDenied:w};j(e.environment,t.orgConfig?.disasterRecoveryRegion)&&await se(u.getClient(K),a,o,t.abortSignal);const $=await ae(u.getClient(T),u.getClient(A),c,o,t.abortSignal),S=te($),N=await n.cdkService.runCdkDestroy(R,c,B(o),s=>o.onCascadeAccountResourceProgress?.(m,s,a),u,!0,D);if(!N.success){const s=N.error;if(s.includes(ne)){l.warn("cascadeDestroy",`CDK destroy failed on ${c} in failed state, retrying via CloudFormation API`,{region:a,account:e.name});try{await re(c,a,D,{accountId:e.id,abortSignal:t.abortSignal},o)}catch(F){const O=`cleanupFailedStack threw for ${c}: ${i(k(F))}`;l.warn("cascadeDestroy",O),o.onLog?.(O,"warn")}const p=await ie(c,u.getClient(T),t.abortSignal);return p.deleted?(S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}):p.error?f(i(p.error)):f(i(`Stack ${c} cleanup attempted but stack still exists in ${a}`))}return f(i(s))}return S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),(y?.primaryRegion===void 0||a===y.primaryRegion)&&await H(u.getClient(V),o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}}C(Ne,"destroyCascadeAccount");async function se(t,n,d,e){try{const r=await U(t,n,e);if(!r.success){l.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:i(r.error.message)});return}if(r.data===null)return;d.onProgress?.({type:"warning",message:G(r.data),metadata:{source:"backup-vault-survival"}}),l.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:r.data.vaultName,recoveryPointCount:r.data.recoveryPointCount,lockPermanent:r.data.lockPermanent})}catch(r){l.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:i(k(r))})}}C(se,"warnIfBackupVaultSurvives");async function ie(t,n,d){try{const r=(await n.send(new M({StackName:t}),{abortSignal:W(d)})).Stacks?.[0]?.StackStatus;return!r||r==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${r} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(oe))return{deleted:!0};const r=i(k(e));return l.debug("cascadeDestroy","Stack verification failed",{error:r}),{deleted:!1,error:`Stack verification failed: ${r}`}}}C(ie,"verifyStackDeleted");export{Ne as destroyCascadeAccount};
1
+ var _=Object.defineProperty;var C=(t,n)=>_(t,"name",{value:n,configurable:!0});import{join as x}from"path";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as i,getErrorMessage as k,resolveSynthEnvironment as L}from"@fjall/util";import{stubCallerIdentity as M}from"../../types/deployment/index.js";import{CloudFormationClient as T,DescribeStacksCommand as K}from"@aws-sdk/client-cloudformation";import{S3Client as A}from"@aws-sdk/client-s3";import{BackupClient as V}from"@aws-sdk/client-backup";import{IAMClient as j}from"@aws-sdk/client-iam";import{accountHasDisasterRecovery as U,describeSurvivingBackupVault as G,formatSurvivingVaultWarning as H}from"../../aws/organisations/backup.js";import{cleanupOrphanedOidcProvider as W}from"../../aws/organisations/oidcProvider.js";import{composeSdkAbortSignal as Y}from"../../aws/organisations/types.js";import{assertLeaseBeforeStack as q}from"../application/leaseGate.js";import{ORGANISATION_TYPES as b,getOrganisationStackName as z}from"../../types/operations.js";import{CdkContextBuilder as J}from"../../services/supporting/CdkContextBuilder.js";import{regionSuffix as Q}from"../../types/config/FjallState.js";import{assertAssemblyEngineCompat as X,buildParamsContext as Z,assumeCascadeRole as ee,forwardOutput as B}from"../contextHelpers.js";import{cascadeOperationKey as te}from"./cascadeHelpers.js";import{capturedSweepBuckets as re,cleanupFailedStack as ae,preEmptyStackBuckets as oe,sweepOrphanedDestroyBuckets as I}from"../stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as ne,STACK_FAILED_STATE_PATTERN as se}from"../../types/constants.js";async function Oe(t,n,c,e,r,a,o,y){const g=Date.now(),m=te(e.name,a),f=C((s,p=s)=>(o.onCascadeAccountComplete?.(m,!1,p,a),{accountName:e.name,accountId:e.id,region:a,success:!1,duration:Date.now()-g,error:s}),"fail");o.onCascadeAccountStart?.(m,e.id,a,r);const v=await ee(n.awsProvider,e.id,a,`fjall-cascade-destroy-${e.name}`);if(!v.success){const s=i(v.error.message);return f(`AssumeRole failed: ${s}`,s)}const{provider:u,credentials:D}=v.data,h=x(c.path,`cdk.out.${e.id}.${Q(a)}`),R=J.buildDeploymentContext({deployType:r,target:c.target,path:c.path,assemblyDir:h,region:a,accountName:e.name,environment:L(e),callerIdentity:M(e.id),...Z({orgConfig:t.orgConfig,identity:t.identity,region:a,primaryRegion:y?.primaryRegion})},{verbose:t.options?.verbose},t.orgConfig);o.onCascadeAccountPhaseChange?.(m,"synth",a);const E=await n.cdkService.runCdkSynth(R,B(o));if(!E.success)return f(i(`Synth failed: ${E.error}`));const P=X(h,o);if(!P.success)return f(P.error.message);o.onCascadeAccountPhaseChange?.(m,"destroy",a);const d=z(r==="platform"?b.PLATFORM:b.ACCOUNT),w=await q(t.assertLeaseForStack,d,t.abortSignal,"destroy");if(w!==void 0)return{...f(w.message),leaseDenied:w};U(e.environment,t.orgConfig?.disasterRecoveryRegion)&&await ie(u.getClient(V),a,o,t.abortSignal);const $=await oe(u.getClient(T),u.getClient(A),d,o,t.abortSignal),S=re($),N=await n.cdkService.runCdkDestroy(R,d,B(o),s=>o.onCascadeAccountResourceProgress?.(m,s,a),u,!0,D);if(!N.success){const s=N.error;if(s.includes(se)){l.warn("cascadeDestroy",`CDK destroy failed on ${d} in failed state, retrying via CloudFormation API`,{region:a,account:e.name});try{await ae(d,a,D,{accountId:e.id,abortSignal:t.abortSignal},o)}catch(F){const O=`cleanupFailedStack threw for ${d}: ${i(k(F))}`;l.warn("cascadeDestroy",O),o.onLog?.(O,"warn")}const p=await ue(d,u.getClient(T),t.abortSignal);return p.deleted?(S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}):p.error?f(i(p.error)):f(i(`Stack ${d} cleanup attempted but stack still exists in ${a}`))}return f(i(s))}return S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),(y?.primaryRegion===void 0||a===y.primaryRegion)&&await W(u.getClient(j),o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}}C(Oe,"destroyCascadeAccount");async function ie(t,n,c,e){try{const r=await G(t,n,e);if(!r.success){l.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:i(r.error.message)});return}if(r.data===null)return;c.onProgress?.({type:"warning",message:H(r.data),metadata:{source:"backup-vault-survival"}}),l.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:r.data.vaultName,recoveryPointCount:r.data.recoveryPointCount,lockPermanent:r.data.lockPermanent})}catch(r){l.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:i(k(r))})}}C(ie,"warnIfBackupVaultSurvives");async function ue(t,n,c){try{const r=(await n.send(new K({StackName:t}),{abortSignal:Y(c)})).Stacks?.[0]?.StackStatus;return!r||r==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${r} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(ne))return{deleted:!0};const r=i(k(e));return l.debug("cascadeDestroy","Stack verification failed",{error:r}),{deleted:!1,error:`Stack verification failed: ${r}`}}}C(ue,"verifyStackDeleted");export{Oe as destroyCascadeAccount};
@@ -1 +1 @@
1
- var Y=Object.defineProperty;var m=(e,o)=>Y(e,"name",{value:o,configurable:!0});import{join as H}from"path";import{success as I,failure as S}from"@fjall/generator";import{logger as A}from"@fjall/util/logger";import{maskSensitiveOutput as R,mapSettledWithConcurrency as X}from"@fjall/util";import{ORGANISATION_TYPES as P,getOrganisationStackName as V}from"../../types/operations.js";import{CdkContextBuilder as q}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as J}from"../../types/deployment/index.js";import{CloudFormationService as G}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as Q,regionSuffix as Z}from"../../types/config/FjallState.js";import{BackupClient as ee}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as te,describeBackupVaultExists as oe}from"../../aws/organisations/backup.js";import{assertAssemblyEngineCompat as ne,buildParamsContext as re,collectStackOutputs as U,assumeCascadeRole as E,forwardOutput as N,reconcileOidcProviderIfCarried as _}from"../contextHelpers.js";import{assertLeaseBeforeStack as ae}from"../application/leaseGate.js";import{deployDelegatedDomain as se}from"../domain/delegatedDomainDeploy.js";import{normaliseRecordName as j}from"../domain/zoneClassifier.js";import{runDriftPreFlightGate as ie}from"../drift/preFlightGate.js";import{accountTier as K}from"@fjall/util";import{buildBootstrapTagsIfResolved as ce}from"../bootstrapTags.js";import{hasOrganisationTierAccount as de}from"../../aws/organisations/accountGlobals.js";import{DEFAULT_REGION as W}from"../../aws/utils/regions.js";const ue=4;function Le(e){const o=e.find(n=>K(n)==="platform"),c=e.filter(n=>K(n)==="account");return{platformAccount:o,memberAccounts:c}}m(Le,"partitionAccounts");function fe(e){const o=e?.primaryRegion??W,c=e?.secondaryRegions??[],n=new Set([o,...c]),i=e?.disasterRecoveryRegion;return i&&n.add(i),[...n]}m(fe,"buildRegionList");function Me(e){return fe(e)[0]??W}m(Me,"cascadeHomeRegion");function le(e,o){return`${e} (${o})`}m(le,"cascadeOperationKey");function Be(e,o){const c=[];for(const n of o)for(const i of e)c.push({account:i,region:n});return c}m(Be,"buildAccountRegionPairs");import{buildCascadeRoleArn as Ke}from"../contextHelpers.js";async function Te(e,o,c,n){c.onLog?.(`Verifying cascade role access for ${o.length} account(s)\u2026`,"info");const i=await X(o,ue,async a=>E(e.awsProvider,a.id,a.region??e.awsProvider.getRegion(),`fjall-preflight-${a.name}`,n)),t=[];return i.forEach((a,s)=>{const r=o[s];if(r){if(a.status==="rejected"){t.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||t.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),t.length===0&&c.onLog?.(`Cascade role access verified for ${o.length} account(s)`,"info"),t}m(Te,"probeCascadeRoles");async function He(e,o,c,n,i,t,a){const s=a?.region??n.region??o.awsProvider.getRegion(),r=le(n.name,s),u=a?.orgConfig??e.orgConfig,C=a?.ipamPoolId;t.onCascadeAccountStart?.(r,n.id,s,i);const p=await E(o.awsProvider,n.id,s,`fjall-cascade-${n.name}`,e.abortSignal);if(!p.success)return t.onCascadeAccountComplete?.(r,!1,R(p.error.message),s),S(new Error(`Failed to assume role for ${n.name}: ${R(p.error.message)}`));const{provider:d,credentials:l}=p.data,h=H(e.workingDirectory,"fjall",i==="platform"?P.PLATFORM:P.ACCOUNT),f=H(h,`cdk.out.${n.id}.${Z(s)}`),w=q.buildDeploymentContext({deployType:i,target:c.target,path:h,assemblyDir:f,environment:n.environment??void 0,region:s,accountName:n.name,callerIdentity:J(n.id),ipamPoolId:C,...re({orgConfig:u,identity:e.identity,skipOidc:e.options?.skipOidc,region:s,primaryRegion:a?.primaryRegion,trailLifecycle:n.trailLifecycle})},{verbose:e.options?.verbose},u);if(te(n.environment,u?.disasterRecoveryRegion)){const g=await oe(d.getClient(ee),e.abortSignal);if(!g.success)return t.onCascadeAccountComplete?.(r,!1,R(g.error.message),s),S(new Error(`Backup vault probe failed for ${n.name}: ${R(g.error.message)}`));w.fjallAdoptBackupVault=g.data}t.onCascadeAccountPhaseChange?.(r,"synth",s);const $=await o.cdkService.runCdkSynth(w,N(t),l);if(!$.success)return t.onCascadeAccountComplete?.(r,!1,R(`Synth failed: ${$.error}`),s),S(new Error(`Synth failed for ${n.name}: ${R($.error)}`));const b=ne(f,t);if(!b.success){const g=R(b.error.message);return t.onCascadeAccountComplete?.(r,!1,g,s),S(new Error(`Engine compatibility check failed for ${n.name}: ${g}`))}const y=V(i==="platform"?P.PLATFORM:P.ACCOUNT),F=Q(h,n.id,s),D=new G(d),{changed:z,currentHash:L}=await o.hashService.compareCascadeStack(f,y,F);if(!z&&e.options?.force!==!0&&await D.stackExists(y,void 0,e.abortSignal)){const g=await D.getStackOutputs(y,void 0,e.abortSignal);g.success||A.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:y,account:n.name});const T=U(g);return await _(w,d,t,e.abortSignal),t.onLog?.(`${n.name}: no infrastructure changes \u2014 skipping deploy`,"info"),t.onCascadeAccountComplete?.(r,!0,void 0,s,T,!0),I({outputs:T,skipped:!0})}const v=await ie(d,{stackNames:[y],cdkOutPath:f,cfnService:D,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(v.kind==="blocked")return t.onCascadeAccountComplete?.(r,!1,v.message,s),S(new Error(v.message));const O=await ae(e.assertLeaseForStack,y,e.abortSignal);if(O!==void 0)return t.onCascadeAccountComplete?.(r,!1,O.message,s),S(O);t.onCascadeAccountPhaseChange?.(r,"bootstrap",s);const x=await o.cdkService.runCdkBootstrap(w,N(t),l,void 0,ce({account:n,estate:de(u?.providerAccounts)?"org":"solo",...a?.orgId!==void 0?{orgId:a.orgId}:{}}));if(!x.success)return t.onCascadeAccountComplete?.(r,!1,R(`Bootstrap failed: ${x.error}`),s),S(new Error(`Bootstrap failed for ${n.name}: ${R(x.error)}`));t.onCascadeAccountPhaseChange?.(r,"deploy",s);const k=await o.cdkService.runCdkDeploy(w,y,N(t),g=>t.onCascadeAccountResourceProgress?.(r,g,s),d,l);if(!k.success)return t.onCascadeAccountComplete?.(r,!1,R(k.error),s),S(new Error(R(k.error)));const M=await D.getStackOutputs(y,void 0,e.abortSignal);M.success||A.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:y,account:n.name});const B=U(M);return await _(w,d,t,e.abortSignal),L!==void 0&&((await o.hashService.persistCascadeStack(F,y,L)).success||A.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:y,account:n.name})),t.onCascadeAccountComplete?.(r,!0,void 0,s,B,!1),I({outputs:B,skipped:!1})}m(He,"deployCascadeAccount");async function Ve(e,o,c,n){const i=new Map,t=e.awsProvider.getRegion(),a=await E(e.awsProvider,o.id,t,`fjall-ipam-read-${o.name}`,n);if(!a.success)return A.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const s=new G(a.data.provider),r=V(P.PLATFORM),u=await s.getStackOutputs(r);if(!u.success)return A.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const C=/^IpamPoolId(\d{12})(\w+)$/;for(const p of u.data){const d=p.OutputKey?.match(C);if(d&&p.OutputValue){const l=`${d[1]}-${d[2]}`;i.set(l,p.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}m(Ve,"readPlatformIpamPoolIds");function pe(e,o){const c=e.account!==void 0&&o?.executingAccountId!==void 0&&e.account!==o.executingAccountId,n=e.region!==void 0&&o?.executingRegion!==void 0&&e.region!==o.executingRegion;if(c)return`[${e.name}] skipped: its fjall-config.json entry records account ${e.account} as its home, but this cascade runs in account ${o?.executingAccountId} - deploy it separately with 'fjall domain deploy ${e.name} -t <target in account ${e.account}>' or run the cascade under that account.`;if(n)return`[${e.name}] skipped: its fjall-config.json entry records region ${e.region} as its home, but this cascade runs in region ${o?.executingRegion} - deploy it separately with 'fjall domain deploy ${e.name} --region ${e.region}', or update the entry's "region" key if this move is intentional.`}m(pe,"foreignDomainSkipReason");function ge(e){const o=new Map;for(const t of e)o.set(j(t.name),t);const c=new Map,n=m((t,a)=>{const s=c.get(t);if(s!==void 0)return s;if(a.includes(t))return[...a.slice(a.indexOf(t)),t];const r=o.get(t);if(r===void 0)return-1;const u=r.parentDomain!==void 0?j(r.parentDomain):void 0,C=u===void 0?-1:n(u,[...a,t]);if(Array.isArray(C))return C;const p=C+1;return c.set(t,p),p},"resolveDepth"),i=[];for(const t of e){const a=n(j(t.name),[]);if(Array.isArray(a)){const s=a.map(r=>o.get(r)?.name??r).join(" -> ");return S(new Error(`Delegated domain ordering: fjall-config.json declares a parentDomain cycle (${s}) - no deploy order satisfies it. Nothing was deployed. Cure: correct the "parentDomain" of one entry in that cycle so parents form a chain, then re-run.`))}(i[a]??=[]).push(t)}return I(i.filter(t=>t.length>0))}m(ge,"planDelegatedDomainWaves");async function Ge(e,o,c){const n=e.getDomains();if(n.length===0)return{domainsDeployed:0,domainsSkipped:0,errors:[]};o.onCascadePhaseStart?.("domains");const i=[];let t=0;for(const d of n){const l=pe(d,c);if(l!==void 0){t++,o.onProgress?.({type:"warning",message:l});continue}i.push(d)}const a=i.filter(d=>d.type==="apex"),s=i.filter(d=>d.type==="delegated");let r=0;const u=[],C=ge(s);if(!C.success)return u.push(C.error.message),o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u};const p=C.data;for(const d of a){const l=await e.deployDomain(d.name,o);l.success?r++:u.push(`${d.name}: ${l.error.message}`)}for(const d of p){const l=await Promise.allSettled(d.map(h=>{const f=e.delegatedDeployer?.(h.name);return f!==void 0?se({deployer:f,callbacks:o,...c?.abortSignal!==void 0?{abortSignal:c.abortSignal}:{},...c?.skipCaaPreflight!==void 0?{skipCaaPreflight:c.skipCaaPreflight}:{}}):e.deployDomain(h.name,o)}));for(let h=0;h<l.length;h++){const f=l[h],w=d[h];if(!(!f||!w))if(f.status==="fulfilled")f.value.success?r++:u.push(`${w.name}: ${f.value.error.message}`);else{const $=f.reason instanceof Error?f.reason.message:String(f.reason);u.push(`${w.name}: ${$}`)}}}return o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u}}m(Ge,"deployDomains");export{ue as CASCADE_MAX_CONCURRENCY,Be as buildAccountRegionPairs,Ke as buildCascadeRoleArn,fe as buildRegionList,Me as cascadeHomeRegion,le as cascadeOperationKey,He as deployCascadeAccount,Ge as deployDomains,Le as partitionAccounts,ge as planDelegatedDomainWaves,Te as probeCascadeRoles,Ve as readPlatformIpamPoolIds};
1
+ var Y=Object.defineProperty;var m=(e,o)=>Y(e,"name",{value:o,configurable:!0});import{join as H}from"path";import{success as I,failure as w}from"@fjall/generator";import{logger as A}from"@fjall/util/logger";import{maskSensitiveOutput as R,mapSettledWithConcurrency as X,resolveSynthEnvironment as q}from"@fjall/util";import{ORGANISATION_TYPES as P,getOrganisationStackName as V}from"../../types/operations.js";import{CdkContextBuilder as J}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as Q}from"../../types/deployment/index.js";import{CloudFormationService as G}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as Z,regionSuffix as ee}from"../../types/config/FjallState.js";import{BackupClient as te}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as oe,describeBackupVaultExists as ne}from"../../aws/organisations/backup.js";import{assertAssemblyEngineCompat as re,buildParamsContext as ae,collectStackOutputs as U,assumeCascadeRole as E,forwardOutput as N,reconcileOidcProviderIfCarried as _}from"../contextHelpers.js";import{assertLeaseBeforeStack as se}from"../application/leaseGate.js";import{deployDelegatedDomain as ie}from"../domain/delegatedDomainDeploy.js";import{normaliseRecordName as j}from"../domain/zoneClassifier.js";import{runDriftPreFlightGate as ce}from"../drift/preFlightGate.js";import{accountTier as K}from"@fjall/util";import{buildBootstrapTagsIfResolved as de}from"../bootstrapTags.js";import{hasOrganisationTierAccount as ue}from"../../aws/organisations/accountGlobals.js";import{DEFAULT_REGION as W}from"../../aws/utils/regions.js";const fe=4;function Me(e){const o=e.find(n=>K(n)==="platform"),c=e.filter(n=>K(n)==="account");return{platformAccount:o,memberAccounts:c}}m(Me,"partitionAccounts");function le(e){const o=e?.primaryRegion??W,c=e?.secondaryRegions??[],n=new Set([o,...c]),i=e?.disasterRecoveryRegion;return i&&n.add(i),[...n]}m(le,"buildRegionList");function Be(e){return le(e)[0]??W}m(Be,"cascadeHomeRegion");function pe(e,o){return`${e} (${o})`}m(pe,"cascadeOperationKey");function Te(e,o){const c=[];for(const n of o)for(const i of e)c.push({account:i,region:n});return c}m(Te,"buildAccountRegionPairs");import{buildCascadeRoleArn as We}from"../contextHelpers.js";async function He(e,o,c,n){c.onLog?.(`Verifying cascade role access for ${o.length} account(s)\u2026`,"info");const i=await X(o,fe,async a=>E(e.awsProvider,a.id,a.region??e.awsProvider.getRegion(),`fjall-preflight-${a.name}`,n)),t=[];return i.forEach((a,s)=>{const r=o[s];if(r){if(a.status==="rejected"){t.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||t.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),t.length===0&&c.onLog?.(`Cascade role access verified for ${o.length} account(s)`,"info"),t}m(He,"probeCascadeRoles");async function Ve(e,o,c,n,i,t,a){const s=a?.region??n.region??o.awsProvider.getRegion(),r=pe(n.name,s),u=a?.orgConfig??e.orgConfig,C=a?.ipamPoolId;t.onCascadeAccountStart?.(r,n.id,s,i);const p=await E(o.awsProvider,n.id,s,`fjall-cascade-${n.name}`,e.abortSignal);if(!p.success)return t.onCascadeAccountComplete?.(r,!1,R(p.error.message),s),w(new Error(`Failed to assume role for ${n.name}: ${R(p.error.message)}`));const{provider:d,credentials:l}=p.data,h=H(e.workingDirectory,"fjall",i==="platform"?P.PLATFORM:P.ACCOUNT),f=H(h,`cdk.out.${n.id}.${ee(s)}`),S=J.buildDeploymentContext({deployType:i,target:c.target,path:h,assemblyDir:f,environment:q(n),region:s,accountName:n.name,callerIdentity:Q(n.id),ipamPoolId:C,...ae({orgConfig:u,identity:e.identity,skipOidc:e.options?.skipOidc,region:s,primaryRegion:a?.primaryRegion,trailLifecycle:n.trailLifecycle})},{verbose:e.options?.verbose},u);if(oe(n.environment,u?.disasterRecoveryRegion)){const g=await ne(d.getClient(te),e.abortSignal);if(!g.success)return t.onCascadeAccountComplete?.(r,!1,R(g.error.message),s),w(new Error(`Backup vault probe failed for ${n.name}: ${R(g.error.message)}`));S.fjallAdoptBackupVault=g.data}t.onCascadeAccountPhaseChange?.(r,"synth",s);const $=await o.cdkService.runCdkSynth(S,N(t),l);if(!$.success)return t.onCascadeAccountComplete?.(r,!1,R(`Synth failed: ${$.error}`),s),w(new Error(`Synth failed for ${n.name}: ${R($.error)}`));const b=re(f,t);if(!b.success){const g=R(b.error.message);return t.onCascadeAccountComplete?.(r,!1,g,s),w(new Error(`Engine compatibility check failed for ${n.name}: ${g}`))}const y=V(i==="platform"?P.PLATFORM:P.ACCOUNT),F=Z(h,n.id,s),D=new G(d),{changed:z,currentHash:L}=await o.hashService.compareCascadeStack(f,y,F);if(!z&&e.options?.force!==!0&&await D.stackExists(y,void 0,e.abortSignal)){const g=await D.getStackOutputs(y,void 0,e.abortSignal);g.success||A.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:y,account:n.name});const T=U(g);return await _(S,d,t,e.abortSignal),t.onLog?.(`${n.name}: no infrastructure changes \u2014 skipping deploy`,"info"),t.onCascadeAccountComplete?.(r,!0,void 0,s,T,!0),I({outputs:T,skipped:!0})}const v=await ce(d,{stackNames:[y],cdkOutPath:f,cfnService:D,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(v.kind==="blocked")return t.onCascadeAccountComplete?.(r,!1,v.message,s),w(new Error(v.message));const O=await se(e.assertLeaseForStack,y,e.abortSignal);if(O!==void 0)return t.onCascadeAccountComplete?.(r,!1,O.message,s),w(O);t.onCascadeAccountPhaseChange?.(r,"bootstrap",s);const x=await o.cdkService.runCdkBootstrap(S,N(t),l,void 0,de({account:n,estate:ue(u?.providerAccounts)?"org":"solo",...a?.orgId!==void 0?{orgId:a.orgId}:{}}));if(!x.success)return t.onCascadeAccountComplete?.(r,!1,R(`Bootstrap failed: ${x.error}`),s),w(new Error(`Bootstrap failed for ${n.name}: ${R(x.error)}`));t.onCascadeAccountPhaseChange?.(r,"deploy",s);const k=await o.cdkService.runCdkDeploy(S,y,N(t),g=>t.onCascadeAccountResourceProgress?.(r,g,s),d,l);if(!k.success)return t.onCascadeAccountComplete?.(r,!1,R(k.error),s),w(new Error(R(k.error)));const M=await D.getStackOutputs(y,void 0,e.abortSignal);M.success||A.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:y,account:n.name});const B=U(M);return await _(S,d,t,e.abortSignal),L!==void 0&&((await o.hashService.persistCascadeStack(F,y,L)).success||A.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:y,account:n.name})),t.onCascadeAccountComplete?.(r,!0,void 0,s,B,!1),I({outputs:B,skipped:!1})}m(Ve,"deployCascadeAccount");async function Ge(e,o,c,n){const i=new Map,t=e.awsProvider.getRegion(),a=await E(e.awsProvider,o.id,t,`fjall-ipam-read-${o.name}`,n);if(!a.success)return A.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const s=new G(a.data.provider),r=V(P.PLATFORM),u=await s.getStackOutputs(r);if(!u.success)return A.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const C=/^IpamPoolId(\d{12})(\w+)$/;for(const p of u.data){const d=p.OutputKey?.match(C);if(d&&p.OutputValue){const l=`${d[1]}-${d[2]}`;i.set(l,p.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}m(Ge,"readPlatformIpamPoolIds");function ge(e,o){const c=e.account!==void 0&&o?.executingAccountId!==void 0&&e.account!==o.executingAccountId,n=e.region!==void 0&&o?.executingRegion!==void 0&&e.region!==o.executingRegion;if(c)return`[${e.name}] skipped: its fjall-config.json entry records account ${e.account} as its home, but this cascade runs in account ${o?.executingAccountId} - deploy it separately with 'fjall domain deploy ${e.name} -t <target in account ${e.account}>' or run the cascade under that account.`;if(n)return`[${e.name}] skipped: its fjall-config.json entry records region ${e.region} as its home, but this cascade runs in region ${o?.executingRegion} - deploy it separately with 'fjall domain deploy ${e.name} --region ${e.region}', or update the entry's "region" key if this move is intentional.`}m(ge,"foreignDomainSkipReason");function me(e){const o=new Map;for(const t of e)o.set(j(t.name),t);const c=new Map,n=m((t,a)=>{const s=c.get(t);if(s!==void 0)return s;if(a.includes(t))return[...a.slice(a.indexOf(t)),t];const r=o.get(t);if(r===void 0)return-1;const u=r.parentDomain!==void 0?j(r.parentDomain):void 0,C=u===void 0?-1:n(u,[...a,t]);if(Array.isArray(C))return C;const p=C+1;return c.set(t,p),p},"resolveDepth"),i=[];for(const t of e){const a=n(j(t.name),[]);if(Array.isArray(a)){const s=a.map(r=>o.get(r)?.name??r).join(" -> ");return w(new Error(`Delegated domain ordering: fjall-config.json declares a parentDomain cycle (${s}) - no deploy order satisfies it. Nothing was deployed. Cure: correct the "parentDomain" of one entry in that cycle so parents form a chain, then re-run.`))}(i[a]??=[]).push(t)}return I(i.filter(t=>t.length>0))}m(me,"planDelegatedDomainWaves");async function Ue(e,o,c){const n=e.getDomains();if(n.length===0)return{domainsDeployed:0,domainsSkipped:0,errors:[]};o.onCascadePhaseStart?.("domains");const i=[];let t=0;for(const d of n){const l=ge(d,c);if(l!==void 0){t++,o.onProgress?.({type:"warning",message:l});continue}i.push(d)}const a=i.filter(d=>d.type==="apex"),s=i.filter(d=>d.type==="delegated");let r=0;const u=[],C=me(s);if(!C.success)return u.push(C.error.message),o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u};const p=C.data;for(const d of a){const l=await e.deployDomain(d.name,o);l.success?r++:u.push(`${d.name}: ${l.error.message}`)}for(const d of p){const l=await Promise.allSettled(d.map(h=>{const f=e.delegatedDeployer?.(h.name);return f!==void 0?ie({deployer:f,callbacks:o,...c?.abortSignal!==void 0?{abortSignal:c.abortSignal}:{},...c?.skipCaaPreflight!==void 0?{skipCaaPreflight:c.skipCaaPreflight}:{}}):e.deployDomain(h.name,o)}));for(let h=0;h<l.length;h++){const f=l[h],S=d[h];if(!(!f||!S))if(f.status==="fulfilled")f.value.success?r++:u.push(`${S.name}: ${f.value.error.message}`);else{const $=f.reason instanceof Error?f.reason.message:String(f.reason);u.push(`${S.name}: ${$}`)}}}return o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u}}m(Ue,"deployDomains");export{fe as CASCADE_MAX_CONCURRENCY,Te as buildAccountRegionPairs,We as buildCascadeRoleArn,le as buildRegionList,Be as cascadeHomeRegion,pe as cascadeOperationKey,Ve as deployCascadeAccount,Ue as deployDomains,Me as partitionAccounts,me as planDelegatedDomainWaves,He as probeCascadeRoles,Ge as readPlatformIpamPoolIds};
@@ -1 +1 @@
1
- var I=Object.defineProperty;var r=(o,n)=>I(o,"name",{value:n,configurable:!0});import{success as d,failure as s}from"@fjall/generator";import{OrganizationsClient as a}from"@aws-sdk/client-organizations";import{CdkContextBuilder as m}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as f}from"../../types/deployment/index.js";import{ensureOrganisationExists as C,describeOrganisation as O,describeOrganisationAsMember as v}from"../../aws/organisations/organisation.js";import{buildParamsContext as A,resolveAccountBootstrapSkipOidc as p}from"../contextHelpers.js";function h(o,n,e,t,i,u,g,l){const c=n.awsProvider.getRegion();return m.buildDeploymentContext({deployType:t,target:e.target,path:e.path,region:c,accountName:u,callerIdentity:f(n.awsProvider.getAccountId()),orgId:i?.orgId,rootId:i?.rootId,managementAccountId:i?.managementAccountId,devOuId:i?.devOuId,...A({orgConfig:o.orgConfig,identity:o.identity,skipOidc:p(t,o.orgConfig,o.options?.skipOidc,l),...t!=="organisation"?{region:c,primaryRegion:o.orgConfig?.primaryRegion}:{},trailLifecycle:g})},{verbose:o.options?.verbose,infraOnly:o.options?.infraOnly},o.orgConfig)}r(h,"buildOrgContext");async function B(o,n){const e=o.awsProvider.getClient(a),t=await C(e,n);return t.success?d({orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}):s(t.error)}r(B,"resolveOrgDetails");async function D(o,n){const e=o.awsProvider.getClient(a),t=await v(e,n);return t.success?t.data===null?d(void 0):d({orgId:t.data.orgId,managementAccountId:t.data.managementAccountId,...t.data.rootId!==void 0?{rootId:t.data.rootId}:{}}):s(t.error)}r(D,"resolveOrgDetailsForMemberComponent");async function F(o,n){const e=o.awsProvider.getClient(a),t=await O(e,n,{tolerateAccessDenied:!0});if(!(!t.success||t.data===null))return{orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}}r(F,"resolveOrgDetailsForSolo");export{h as buildOrgContext,B as resolveOrgDetails,D as resolveOrgDetailsForMemberComponent,F as resolveOrgDetailsForSolo};
1
+ var m=Object.defineProperty;var r=(o,n)=>m(o,"name",{value:n,configurable:!0});import{success as d,failure as u}from"@fjall/generator";import{OrganizationsClient as a}from"@aws-sdk/client-organizations";import{CdkContextBuilder as f}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as C}from"../../types/deployment/index.js";import{ensureOrganisationExists as v,describeOrganisation as O,describeOrganisationAsMember as A}from"../../aws/organisations/organisation.js";import{buildParamsContext as p,resolveAccountBootstrapSkipOidc as b}from"../contextHelpers.js";import{resolveDeployEnvironment as w}from"../deployEnvironment.js";function E(o,n,e,t,i,c,l,I){const s=n.awsProvider.getRegion(),g=w({orgConfig:o.orgConfig,accountId:n.awsProvider.getAccountId(),accountName:c});return f.buildDeploymentContext({deployType:t,target:e.target,path:e.path,region:s,accountName:c,...g.success?{environment:g.data}:{},callerIdentity:C(n.awsProvider.getAccountId()),orgId:i?.orgId,rootId:i?.rootId,managementAccountId:i?.managementAccountId,devOuId:i?.devOuId,...p({orgConfig:o.orgConfig,identity:o.identity,skipOidc:b(t,o.orgConfig,o.options?.skipOidc,I),...t!=="organisation"?{region:s,primaryRegion:o.orgConfig?.primaryRegion}:{},trailLifecycle:l})},{verbose:o.options?.verbose,infraOnly:o.options?.infraOnly},o.orgConfig)}r(E,"buildOrgContext");async function F(o,n){const e=o.awsProvider.getClient(a),t=await v(e,n);return t.success?d({orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}):u(t.error)}r(F,"resolveOrgDetails");async function M(o,n){const e=o.awsProvider.getClient(a),t=await A(e,n);return t.success?t.data===null?d(void 0):d({orgId:t.data.orgId,managementAccountId:t.data.managementAccountId,...t.data.rootId!==void 0?{rootId:t.data.rootId}:{}}):u(t.error)}r(M,"resolveOrgDetailsForMemberComponent");async function S(o,n){const e=o.awsProvider.getClient(a),t=await O(e,n,{tolerateAccessDenied:!0});if(!(!t.success||t.data===null))return{orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}}r(S,"resolveOrgDetailsForSolo");export{E as buildOrgContext,F as resolveOrgDetails,M as resolveOrgDetailsForMemberComponent,S as resolveOrgDetailsForSolo};
@@ -23,10 +23,26 @@ export declare class CdkArgumentBuilder {
23
23
  */
24
24
  buildParameterArgs(parameters?: Record<string, string>, stackName?: string): string[];
25
25
  injectCascadeCredentials(env: NodeJS.ProcessEnv, credentials?: CdkOptions["credentials"]): void;
26
+ /**
27
+ * Build the CDK subprocess environment.
28
+ *
29
+ * `ENVIRONMENT` is set from the resolved context — and MUST be, not merely
30
+ * defaulted. The constructs package resolves the environment through two
31
+ * readers with OPPOSITE precedence: `getEnvironment()` (utils/env.ts, pre-App)
32
+ * reads `process.env.ENVIRONMENT` first and `-c environment=` second, while
33
+ * `getConfig()` (utils/getConfig.ts, App-scoped) reads `-c environment=`
34
+ * first and `process.env.ENVIRONMENT` last. Supplying only one channel lets
35
+ * an ambient `ENVIRONMENT` in the operator's shell win in one reader and lose
36
+ * in the other, so the same synth can classify a stack two different ways.
37
+ * Writing both makes them agree unconditionally and overwrites whatever the
38
+ * shell happened to export — which is the point: deploy-core resolved this
39
+ * account's stage from the org config, and that answer is authoritative.
40
+ */
26
41
  buildCdkEnv(options?: {
27
42
  context?: {
28
43
  region?: string;
29
44
  accountId?: string;
45
+ environment?: string;
30
46
  };
31
47
  credentials?: CdkOptions["credentials"];
32
48
  }): NodeJS.ProcessEnv;
@@ -1 +1 @@
1
- var d=Object.defineProperty;var s=(o,a)=>d(o,"name",{value:a,configurable:!0});import{CAPACITY_IDENTITY_CONTEXT_KEY as f,filterDangerousEnvVars as c}from"@fjall/util";class A{static{s(this,"CdkArgumentBuilder")}buildContextArgs(a){const e=[];if(a?.accountId&&e.push("-c",`accountId=${a.accountId}`),a?.environment&&e.push("-c",`environment=${a.environment}`),a?.managedAccount&&e.push("-c","managedAccount=true"),a?.accountName&&e.push("-c",`accountName=${a.accountName}`),a?.orgId&&e.push("-c",`orgId=${a.orgId}`),a?.rootId&&e.push("-c",`rootId=${a.rootId}`),a?.managementAccountId&&e.push("-c",`managementAccountId=${a.managementAccountId}`),a?.devOuId&&e.push("-c",`fjallDevOuId=${a.devOuId}`),a?.ipamPoolId&&e.push("-c",`ipamPoolId=${a.ipamPoolId}`),a?.fjallOrgId&&e.push("-c",`fjallOrgId=${a.fjallOrgId}`),a?.fjallOidcConfigured&&e.push("-c",`fjallOidcConfigured=${a.fjallOidcConfigured}`),a?.fjallAccountGlobalsConfigured&&e.push("-c",`fjallAccountGlobalsConfigured=${a.fjallAccountGlobalsConfigured}`),a?.fjallAccountTrailState&&e.push("-c",`fjallAccountTrailState=${a.fjallAccountTrailState}`),a?.orgConfig&&e.push("-c",`orgConfig=${a.orgConfig}`),a?.fjallAdoptBackupVault&&e.push("-c","fjallAdoptBackupVault=true"),a?.resolvedSecretArns&&e.push("-c",`fjallResolvedSecretArns=${a.resolvedSecretArns}`),a?.managedDomainBindings)for(const[i,n]of Object.entries(a.managedDomainBindings))n!==""&&e.push("-c",`fjall:managedDomain:${i}=${n}`);return a?.domainDeployPhase&&e.push("-c",`fjall:domainDeployPhase=${a.domainDeployPhase}`),a?.parentDelegationRoleArn&&e.push("-c",`fjall:parentDelegationRoleArn=${a.parentDelegationRoleArn}`),a?.capacityIdentity&&e.push("-c",`${f}=${a.capacityIdentity}`),e}buildParameterArgs(a,e){if(a===void 0)return[];const i=Object.entries(a);if(i.length===0)return[];const n=e!==void 0&&e!==""?`${e}:`:"",u=[];for(const[r,l]of i){if(!/^[A-Za-z][A-Za-z0-9]*$/.test(r))throw new Error(`Invalid CloudFormation parameter name "${r}": must match /^[A-Za-z][A-Za-z0-9]*$/ (alphanumeric, leading letter, no separators).`);if(l==="")throw new Error(`CloudFormation parameter "${r}" has an empty value.`);if(/[,\n\r]/.test(l))throw new Error(`CloudFormation parameter "${r}" value contains "," or newline \u2014 cdk's --parameters splits on "," so the deploy would silently fragment.`);u.push("--parameters",`${n}${r}=${l}`)}return u}injectCascadeCredentials(a,e){e&&(a.AWS_ACCESS_KEY_ID=e.accessKeyId,a.AWS_SECRET_ACCESS_KEY=e.secretAccessKey,delete a.AWS_SESSION_TOKEN,e.sessionToken&&(a.AWS_SESSION_TOKEN=e.sessionToken))}buildCdkEnv(a){const e={...c(process.env),CI:"true",FORCE_COLOR:"0",CDK_DISABLE_VERSION_CHECK:"1",NODE_NO_WARNINGS:"1"};return a?.context?.region&&(e.AWS_REGION=a.context.region,e.AWS_DEFAULT_REGION=a.context.region,e.CDK_DEFAULT_REGION=a.context.region),a?.context?.accountId&&(e.CDK_DEFAULT_ACCOUNT=a.context.accountId),this.injectCascadeCredentials(e,a?.credentials),e}}export{A as CdkArgumentBuilder};
1
+ var s=Object.defineProperty;var d=(o,a)=>s(o,"name",{value:a,configurable:!0});import{CAPACITY_IDENTITY_CONTEXT_KEY as f,filterDangerousEnvVars as c}from"@fjall/util";class m{static{d(this,"CdkArgumentBuilder")}buildContextArgs(a){const e=[];if(a?.accountId&&e.push("-c",`accountId=${a.accountId}`),a?.environment&&e.push("-c",`environment=${a.environment}`),a?.managedAccount&&e.push("-c","managedAccount=true"),a?.accountName&&e.push("-c",`accountName=${a.accountName}`),a?.orgId&&e.push("-c",`orgId=${a.orgId}`),a?.rootId&&e.push("-c",`rootId=${a.rootId}`),a?.managementAccountId&&e.push("-c",`managementAccountId=${a.managementAccountId}`),a?.devOuId&&e.push("-c",`fjallDevOuId=${a.devOuId}`),a?.ipamPoolId&&e.push("-c",`ipamPoolId=${a.ipamPoolId}`),a?.fjallOrgId&&e.push("-c",`fjallOrgId=${a.fjallOrgId}`),a?.fjallOidcConfigured&&e.push("-c",`fjallOidcConfigured=${a.fjallOidcConfigured}`),a?.fjallAccountGlobalsConfigured&&e.push("-c",`fjallAccountGlobalsConfigured=${a.fjallAccountGlobalsConfigured}`),a?.fjallAccountTrailState&&e.push("-c",`fjallAccountTrailState=${a.fjallAccountTrailState}`),a?.orgConfig&&e.push("-c",`orgConfig=${a.orgConfig}`),a?.fjallAdoptBackupVault&&e.push("-c","fjallAdoptBackupVault=true"),a?.resolvedSecretArns&&e.push("-c",`fjallResolvedSecretArns=${a.resolvedSecretArns}`),a?.managedDomainBindings)for(const[n,i]of Object.entries(a.managedDomainBindings))i!==""&&e.push("-c",`fjall:managedDomain:${n}=${i}`);return a?.domainDeployPhase&&e.push("-c",`fjall:domainDeployPhase=${a.domainDeployPhase}`),a?.parentDelegationRoleArn&&e.push("-c",`fjall:parentDelegationRoleArn=${a.parentDelegationRoleArn}`),a?.capacityIdentity&&e.push("-c",`${f}=${a.capacityIdentity}`),e}buildParameterArgs(a,e){if(a===void 0)return[];const n=Object.entries(a);if(n.length===0)return[];const i=e!==void 0&&e!==""?`${e}:`:"",u=[];for(const[r,l]of n){if(!/^[A-Za-z][A-Za-z0-9]*$/.test(r))throw new Error(`Invalid CloudFormation parameter name "${r}": must match /^[A-Za-z][A-Za-z0-9]*$/ (alphanumeric, leading letter, no separators).`);if(l==="")throw new Error(`CloudFormation parameter "${r}" has an empty value.`);if(/[,\n\r]/.test(l))throw new Error(`CloudFormation parameter "${r}" value contains "," or newline \u2014 cdk's --parameters splits on "," so the deploy would silently fragment.`);u.push("--parameters",`${i}${r}=${l}`)}return u}injectCascadeCredentials(a,e){e&&(a.AWS_ACCESS_KEY_ID=e.accessKeyId,a.AWS_SECRET_ACCESS_KEY=e.secretAccessKey,delete a.AWS_SESSION_TOKEN,e.sessionToken&&(a.AWS_SESSION_TOKEN=e.sessionToken))}buildCdkEnv(a){const e={...c(process.env),CI:"true",FORCE_COLOR:"0",CDK_DISABLE_VERSION_CHECK:"1",NODE_NO_WARNINGS:"1"};return a?.context?.region&&(e.AWS_REGION=a.context.region,e.AWS_DEFAULT_REGION=a.context.region,e.CDK_DEFAULT_REGION=a.context.region),a?.context?.accountId&&(e.CDK_DEFAULT_ACCOUNT=a.context.accountId),a?.context?.environment&&(e.ENVIRONMENT=a.context.environment),this.injectCascadeCredentials(e,a?.credentials),e}}export{m as CdkArgumentBuilder};
@@ -5,6 +5,20 @@ import type { AwsProvider } from "../../aws/AwsProvider.js";
5
5
  import type { EventLogWriterFactory } from "../../aws/utils/cloudformationEvents.js";
6
6
  /** Deploy/destroy default — CloudFormation stacks can take up to 30 minutes */
7
7
  export declare const DEFAULT_DEPLOY_TIMEOUT_MS = 1800000;
8
+ /**
9
+ * Total wall-clock budget for one monitored deploy, covering the CDK child AND
10
+ * any re-attach that follows it killing. Both halves draw on this one number,
11
+ * so a deploy cannot quietly take twice what was asked for.
12
+ *
13
+ * A stack that legitimately outruns 30 minutes — Aurora Global, a CloudFront
14
+ * distribution, an RDS restore — is not a failure, it is a deploy whose budget
15
+ * was set by someone who had not met it. `FJALL_DEPLOY_TIMEOUT_MS` is how the
16
+ * operator says so; the deploy-test harness sets it from each suite's declared
17
+ * `deployMs`. A non-numeric or non-positive value throws rather than falling
18
+ * back, because silently deploying under a budget the operator did not choose
19
+ * is the failure this whole seam exists to stop.
20
+ */
21
+ export declare function resolveDeployTimeoutMs(): number;
8
22
  export declare function startStackMonitoring(monitor: CloudFormationEventMonitor, stackName: string, onResourceProgress?: (event: ResourceEvent) => void): Promise<{
9
23
  success: boolean;
10
24
  status?: string;
@@ -1 +1 @@
1
- var y=Object.defineProperty;var a=(t,e)=>y(t,"name",{value:e,configurable:!0});import{logger as d}from"@fjall/util/logger";import{getErrorMessage as g,sleep as F}from"@fjall/util";import{CloudFormationFailureAnalyser as m}from"@fjall/util/aws";const p=18e5;function v(t,e,n){return t.waitForStackComplete(e,{timeout:p,pollInterval:2e3,onResourceUpdate:n,onStackComplete:a((o,r)=>{},"onStackComplete")}).catch(o=>(d.debug("CdkService","Stack monitoring failed",{error:g(o)}),{success:!1,failureReason:"Monitoring failed"}))}a(v,"startStackMonitoring");const A=1e4,f=200;async function w(t,e,n=A){if(t===null)return null;if(e===null)return t.getFailureAnalysis();const o=Date.now()+n;let r;const s=new Promise(i=>{r=setTimeout(()=>i("grace-elapsed"),n)});try{if(await Promise.race([e,s])==="grace-elapsed")return t.getFailureAnalysis()}finally{r!==void 0&&clearTimeout(r)}for(;t.getFailureAnalysis()===null&&Date.now()<o;)await F(f);return t.getFailureAnalysis()}a(w,"settleMonitoringAnalysis");class E{static{a(this,"CdkEventMonitor")}eventLogWriterFactory;onFailureAnalysis;constructor(e){this.eventLogWriterFactory=e?.eventLogWriterFactory,this.onFailureAnalysis=e?.onFailureAnalysis}async createEventMonitor(e,n,o,r,s){const i=await import("../../aws/utils/cloudformationEvents.js"),l=new i.CloudFormationEventMonitor(s,{failureAnalyser:new m,eventLogWriterFactory:this.eventLogWriterFactory,...this.onFailureAnalysis!==void 0?{onFailureAnalysis:this.onFailureAnalysis}:{}}),c=`${e}-${Date.now()}`,u=r.target||r.deployType||"deployment";return l.enableLogging(c,n,o,u),l}}export{E as CdkEventMonitor,p as DEFAULT_DEPLOY_TIMEOUT_MS,A as MONITORING_ANALYSIS_GRACE_MS,w as settleMonitoringAnalysis,v as startStackMonitoring};
1
+ var y=Object.defineProperty;var i=(e,t)=>y(e,"name",{value:t,configurable:!0});import{logger as m}from"@fjall/util/logger";import{getErrorMessage as g,sleep as f}from"@fjall/util";import{CloudFormationFailureAnalyser as p}from"@fjall/util/aws";const u=18e5;function w(){const e=process.env.FJALL_DEPLOY_TIMEOUT_MS;if(e===void 0||e==="")return u;if(!/^\d+$/.test(e.trim()))throw new Error(`FJALL_DEPLOY_TIMEOUT_MS must be a whole number of milliseconds (digits only, no unit suffix); received "${e}".`);const t=parseInt(e.trim(),10);if(t<=0)throw new Error(`FJALL_DEPLOY_TIMEOUT_MS must be greater than zero; received "${e}".`);return t}i(w,"resolveDeployTimeoutMs");function S(e,t,n){return e.waitForStackComplete(t,{timeout:u,pollInterval:2e3,onResourceUpdate:n,onStackComplete:i((o,r)=>{},"onStackComplete")}).catch(o=>(m.debug("CdkService","Stack monitoring failed",{error:g(o)}),{success:!1,failureReason:"Monitoring failed"}))}i(S,"startStackMonitoring");const F=1e4,_=200;async function T(e,t,n=F){if(e===null)return null;if(t===null)return e.getFailureAnalysis();const o=Date.now()+n;let r;const s=new Promise(a=>{r=setTimeout(()=>a("grace-elapsed"),n)});try{if(await Promise.race([t,s])==="grace-elapsed")return e.getFailureAnalysis()}finally{r!==void 0&&clearTimeout(r)}for(;e.getFailureAnalysis()===null&&Date.now()<o;)await f(_);return e.getFailureAnalysis()}i(T,"settleMonitoringAnalysis");class v{static{i(this,"CdkEventMonitor")}eventLogWriterFactory;onFailureAnalysis;constructor(t){this.eventLogWriterFactory=t?.eventLogWriterFactory,this.onFailureAnalysis=t?.onFailureAnalysis}async createEventMonitor(t,n,o,r,s){const a=await import("../../aws/utils/cloudformationEvents.js"),l=new a.CloudFormationEventMonitor(s,{failureAnalyser:new p,eventLogWriterFactory:this.eventLogWriterFactory,...this.onFailureAnalysis!==void 0?{onFailureAnalysis:this.onFailureAnalysis}:{}}),c=`${t}-${Date.now()}`,d=r.target||r.deployType||"deployment";return l.enableLogging(c,n,o,d),l}}export{v as CdkEventMonitor,u as DEFAULT_DEPLOY_TIMEOUT_MS,F as MONITORING_ANALYSIS_GRACE_MS,w as resolveDeployTimeoutMs,T as settleMonitoringAnalysis,S as startStackMonitoring};
@@ -36,6 +36,12 @@ export declare function detectStackNameFromChunk(chunk: string): StackDetectionR
36
36
  export interface DeployMonitoringState {
37
37
  cdkOutput: string;
38
38
  actualStackName: string;
39
+ /**
40
+ * True when `actualStackName` came from the caller naming the stack exactly,
41
+ * false when it is `getFallbackStackName`'s guess. Output detection may
42
+ * correct a guess; it must never overrule a name the caller knew.
43
+ */
44
+ stackNameIsAuthoritative: boolean;
39
45
  stackDetected: boolean;
40
46
  monitoringPromise: Promise<{
41
47
  success: boolean;
@@ -1,2 +1,2 @@
1
- var m=Object.defineProperty;var i=(e,t)=>m(e,"name",{value:t,configurable:!0});import{logger as k}from"@fjall/util/logger";import{maskSensitiveOutput as l}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as S,CDK_NO_STACKS_MATCH as d}from"../../aws/utils/cloudformationEvents.js";import{success as n,failure as p}from"@fjall/generator";import{formatInfrastructureError as N}from"./CdkErrorFormatter.js";import{startStackMonitoring as h}from"./CdkEventMonitoring.js";function T(e,t,s){const c=new RegExp(`${s}[:\\s|]*.*?(?:stack has no resources|skipping deployment)`,"i").test(e),r=e.includes("no changes")||e.includes("Stack is up to date")||e.includes("No changes to deploy"),f=e.includes(d)||!t.success&&t.error.includes(d);if(t.success)return n({message:c?"Stack has no resources, skipped":r?"Infrastructure already up to date":"Deployment successful",status:r?"NO_CHANGES":void 0,details:{output:e,skipped:c}});if(f)return n({message:"Stack not defined, skipped",status:"SKIPPED",details:{output:e,skipped:!0}});let a=l(t.error||"Deployment failed");if(e.includes("failed:")){const u=e.match(/failed: .*?: (.*?)(?:\n|$)/);u&&(a=l(u[1]))}return k.error("CdkService","CDK deployment returned failure",{errorMessage:a}),p(a)}i(T,"analyseDeployOutput");function R(e){const t=[e.summary],{resource:s,reason:o}=e.rootCause;if(t.push(`Root cause: ${s.logicalId} \u2014 ${o}`),e.driftSuspects!==void 0&&e.driftSuspects.length>0){const c=e.driftSuspects.map(r=>`${r.logicalId} (${r.resourceType})`).join(", ");t.push(`Out-of-band deletion suspected: ${c}. Run 'fjall drift detect' to confirm; confirmed deletions are remediated with 'fjall drift repair'.`)}return t.join(`
2
- `)}i(R,"formatAnalysedDeployFailure");function M(e){return e.includes("Access Denied")||e.includes("AccessDenied")?"Access denied. Check your AWS credentials and permissions":e.includes("ExpiredToken")?"AWS credentials have expired. Please refresh your credentials":e.includes("ENOTFOUND")||e.includes("ECONNREFUSED")?"Network error. Please check your internet connection and try again":e.includes("timeout")?"Bootstrap operation timed out":N(e)}i(M,"analyseBootstrapError");function _(e){return e.success?n({message:"Stack destroyed successfully"}):e.error.includes(S)||e.error.includes(d)?n({message:"Stack already deleted or does not exist",details:{skipped:!0}}):p(e.error||"Destroy failed")}i(_,"analyseDestroyResult");function y(e){const t=e.match(/Deploying stack\s+([^\s]+)/);if(t)return{detected:!0,stackName:t[1]};const s=e.match(/^([^\s|]+)\s*\|\s*\d+\/\d+/m);if(s)return{detected:!0,stackName:s[1]};const o=e.match(/arn:aws:cloudformation:[^:]+:[^:]+:stack\/([^/]+)\//);return o?{detected:!0,stackName:o[1]}:{detected:!1}}i(y,"detectStackNameFromChunk");function $(e,t,s,o){return c=>{if(e.cdkOutput+=c,t?.(c),!e.stackDetected){const r=y(c);r.detected&&r.stackName&&(e.actualStackName=r.stackName,e.stackDetected=!0),e.stackDetected&&s&&!e.monitoringPromise&&(e.monitoringPromise=h(s,e.actualStackName,o))}}}i($,"createEnhancedOutputCallback");export{M as analyseBootstrapError,T as analyseDeployOutput,_ as analyseDestroyResult,$ as createEnhancedOutputCallback,y as detectStackNameFromChunk,R as formatAnalysedDeployFailure};
1
+ var p=Object.defineProperty;var i=(e,t)=>p(e,"name",{value:t,configurable:!0});import{logger as k}from"@fjall/util/logger";import{maskSensitiveOutput as l}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as N,CDK_NO_STACKS_MATCH as d}from"../../aws/utils/cloudformationEvents.js";import{success as a,failure as f}from"@fjall/generator";import{formatInfrastructureError as S}from"./CdkErrorFormatter.js";import{startStackMonitoring as h}from"./CdkEventMonitoring.js";function T(e,t,s){const c=new RegExp(`${s}[:\\s|]*.*?(?:stack has no resources|skipping deployment)`,"i").test(e),r=e.includes("no changes")||e.includes("Stack is up to date")||e.includes("No changes to deploy"),m=e.includes(d)||!t.success&&t.error.includes(d);if(t.success)return a({message:c?"Stack has no resources, skipped":r?"Infrastructure already up to date":"Deployment successful",status:r?"NO_CHANGES":void 0,details:{output:e,skipped:c}});if(m)return a({message:"Stack not defined, skipped",status:"SKIPPED",details:{output:e,skipped:!0}});let n=l(t.error||"Deployment failed");if(e.includes("failed:")){const u=e.match(/failed: .*?: (.*?)(?:\n|$)/);u&&(n=l(u[1]))}return k.error("CdkService","CDK deployment returned failure",{errorMessage:n}),f(n)}i(T,"analyseDeployOutput");function R(e){const t=[e.summary],{resource:s,reason:o}=e.rootCause;if(t.push(`Root cause: ${s.logicalId} \u2014 ${o}`),e.driftSuspects!==void 0&&e.driftSuspects.length>0){const c=e.driftSuspects.map(r=>`${r.logicalId} (${r.resourceType})`).join(", ");t.push(`Out-of-band deletion suspected: ${c}. Run 'fjall drift detect' to confirm; confirmed deletions are remediated with 'fjall drift repair'.`)}return t.join(`
2
+ `)}i(R,"formatAnalysedDeployFailure");function M(e){return e.includes("Access Denied")||e.includes("AccessDenied")?"Access denied. Check your AWS credentials and permissions":e.includes("ExpiredToken")?"AWS credentials have expired. Please refresh your credentials":e.includes("ENOTFOUND")||e.includes("ECONNREFUSED")?"Network error. Please check your internet connection and try again":e.includes("timeout")?"Bootstrap operation timed out":S(e)}i(M,"analyseBootstrapError");function _(e){return e.success?a({message:"Stack destroyed successfully"}):e.error.includes(N)||e.error.includes(d)?a({message:"Stack already deleted or does not exist",details:{skipped:!0}}):f(e.error||"Destroy failed")}i(_,"analyseDestroyResult");function y(e){const t=e.match(/Deploying stack\s+([^\s]+)/);if(t)return{detected:!0,stackName:t[1]};const s=e.match(/^([^\s|]+)\s*\|\s*\d+\/\d+/m);if(s)return{detected:!0,stackName:s[1]};const o=e.match(/arn:aws:cloudformation:[^:]+:[^:]+:stack\/([^/]+)\//);return o?{detected:!0,stackName:o[1]}:{detected:!1}}i(y,"detectStackNameFromChunk");function $(e,t,s,o){return c=>{if(e.cdkOutput+=c,t?.(c),!e.stackDetected){const r=y(c);r.detected&&r.stackName&&(e.stackNameIsAuthoritative||(e.actualStackName=r.stackName),e.stackDetected=!0),e.stackDetected&&s&&!e.monitoringPromise&&(e.monitoringPromise=h(s,e.actualStackName,o))}}}i($,"createEnhancedOutputCallback");export{M as analyseBootstrapError,T as analyseDeployOutput,_ as analyseDestroyResult,$ as createEnhancedOutputCallback,y as detectStackNameFromChunk,R as formatAnalysedDeployFailure};
@@ -1,15 +1,16 @@
1
- var H=Object.defineProperty;var M=(_,t)=>H(_,"name",{value:t,configurable:!0});import{existsSync as z}from"fs";import{join as K}from"path";import{logger as I}from"@fjall/util/logger";import{isSuccessState as L,isTerminalState as W}from"../../aws/utils/cloudformationEventHelpers.js";import{success as b,failure as a}from"@fjall/generator";import{DEFAULT_REGION as C}from"../../aws/utils/regions.js";import{getErrorMessage as D,maskSensitiveOutput as S}from"@fjall/util";import{CdkEventMonitor as J,DEFAULT_DEPLOY_TIMEOUT_MS as Q,settleMonitoringAnalysis as B,startStackMonitoring as V}from"./CdkEventMonitoring.js";import{analyseDeployOutput as Z,analyseDestroyResult as x,createEnhancedOutputCallback as tt,formatAnalysedDeployFailure as N}from"./CdkOutputAnalyser.js";import{recordDriftSuspects as et}from"../../orchestration/drift/driftSuspectJournal.js";import{CdkCommandRunner as rt}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as nt}from"./CdkArgumentBuilder.js";import{CdkProcessManager as ot}from"./CdkProcessManager.js";import{cancelInFlightStack as j}from"./cancelInFlightStack.js";import{awaitToolkitSettled as U,filterBootstrapTagEntries as at,probeToolkitStack as it,resolveBootstrapForce as st,MAX_BOOTSTRAP_LOST_RACE_RETRIES as G,TOOLKIT_SETTLE_BUDGET_MS as ct}from"./bootstrapConvergence.js";import{isAborted as k}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as lt}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as ut,resolveStackName as X,getFallbackStackName as dt,buildDeploymentCdkContext as $}from"./cdkServiceHelpers.js";import{ensureDependencyIntegrity as ft}from"./dependencyIntegrityGate.js";import{runInfraTypecheckPreflight as mt}from"./infraTypecheckPreflight.js";import{isTransientNetworkFailure as pt,isReattachableInProgressStatus as gt,MAX_TRANSIENT_NETWORK_RETRIES as E,TRANSIENT_RETRY_BACKOFF_MS as ht}from"./transientNetworkRecovery.js";import{sleepAbortable as yt}from"../../util/sleepAbortable.js";class jt{static{M(this,"CdkService")}commandRunner;eventMonitor;abortSignal;constructor(t){const e=t?.processManager??new ot(new nt,t?.abortSignal);this.commandRunner=new rt(e),this.eventMonitor=new J({eventLogWriterFactory:t?.eventLogWriterFactory,...t?.onFailureAnalysis!==void 0?{onFailureAnalysis:t.onFailureAnalysis}:{}}),this.abortSignal=t?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(t,e,r){return this.commandRunner.checkDifferences(t,e,r)}async deploy(t,e,r){return this.commandRunner.deploy(t,e,r)}async destroy(t,e,r){return this.commandRunner.destroy(t,e,r)}async runImport(t,e,r){return this.commandRunner.runImport(t,e,r)}async synth(t,e){return this.commandRunner.synth(t,e)}async bootstrap(t,e,r){return this.commandRunner.bootstrap(t,e,r)}async runCdkSynth(t,e,r){const i=t.callerIdentity?.Account;try{const s=await ft(t.path,{...e!==void 0?{onOutput:e}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});if(!s.success)return a(s.error);const o=await mt(t.path,{...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});o.status==="failed"&&(e?.(`WARNING: TypeScript errors in ${t.path} \u2014 the synthesised template may be wrong. This check will block deploys in a future release.
2
- ${o.details}
3
- `),I.warn("CdkService","Infrastructure typecheck failed",{path:t.path}));const n=await this.synth(t.path,{outputCallback:e,context:$(t,i,t.region||C),...t.assemblyDir!==void 0?{outputDir:t.assemblyDir}:{},...r!==void 0?{credentials:r}:{}});return n.success?b({message:"CloudFormation template synthesised",details:n.data.output?{synthesisTime:n.data.output}:void 0}):a(n.error||"Failed to synthesise CloudFormation template")}catch(s){return a(`CDK synth failed: ${S(D(s))}`)}}async runCdkBootstrap(t,e,r,i,s){const o=t.callerIdentity?.Account,n=t.region||C;try{if(!o)return a("No AWS account ID available");const u=K(t.path,"node_modules");if(!z(u))return a(`Dependencies not installed. Please run 'npm install' in ${t.path} before deploying.`);const d=at(s),c=Object.keys(d).length>0,m={region:n,...r!==void 0?{credentials:r}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}};for(let p=0;p<=G;p++){if(k(this.abortSignal))return a("Bootstrap cancelled before completion");let f=await it(m);if(f.kind==="in-progress"&&(e?.(`Another deployment is updating the CDK toolkit in ${o}/${n} \u2014 waiting for it to finish...
4
- `),f=await U(m)),k(this.abortSignal))return a("Bootstrap cancelled before completion");if(f.kind==="in-progress")return a(`The CDKToolkit stack in ${o}/${n} has been ${f.status} for over ${Math.round(ct/6e4)} minutes. Another deployment may be stuck bootstrapping this environment \u2014 wait for it to finish (or cancel it in the CloudFormation console), then retry.`);const y=await this.bootstrap(o,n,{outputCallback:e,credentials:r,bootstrapForce:st(f,c?d:void 0),...i!==void 0&&i!==""?{permissionsBoundary:i}:{},...c?{bootstrapTags:d}:{}});if(y.success)return b({message:"AWS environment bootstrapped"});if(k(this.abortSignal))return a("Bootstrap cancelled before completion");if(y.error.kind==="lost-race"&&p<G){I.warn("CdkService","Concurrent bootstrap detected \u2014 waiting for it to settle before retrying",{accountId:o,region:n,attempt:p+1}),e?.(`Another deployment is bootstrapping ${o}/${n} \u2014 waiting for it to finish before retrying...
5
- `),await U(m);continue}return a(y.error.message||"Failed to bootstrap AWS environment")}return a("Failed to bootstrap AWS environment")}catch(u){return a(`CDK bootstrap failed: ${S(D(u))}`)}}async runCdkDiff(t,e){const r=t.callerIdentity?.Account;try{const i=await this.checkDifferences(t.path,void 0,{verbose:t.options?.verbose,outputCallback:e,context:$(t,r,t.region||C)});return i.success?b({message:"Diff check complete",details:{hasDifferences:i.data.hasDifferences,details:i.data.details}}):a(`CDK diff failed: ${i.error.message}`)}catch(i){return a(`CDK diff failed: ${S(D(i))}`)}}async runCdkDeploy(t,e,r,i,s,o,n,u){const d=t.callerIdentity?.Account,c=t.region||C;if(!d)return a("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!s)return a("AwsProvider is required for deployment monitoring.");const m=t.assemblyDir??K(t.path,"cdk.out"),p=lt(m,i);let f=null,y;try{const h=X(e,t)??dt(t),g=await this.eventMonitor.createEventMonitor("deploy",h,c,t,s);f=g,r&&(r(S(`Starting CloudFormation deployment of ${h}...
1
+ var V=Object.defineProperty;var P=(O,t)=>V(O,"name",{value:t,configurable:!0});import{existsSync as Z}from"fs";import{join as G}from"path";import{logger as I}from"@fjall/util/logger";import{isSuccessState as U,isTerminalState as X}from"../../aws/utils/cloudformationEventHelpers.js";import{success as b,failure as n}from"@fjall/generator";import{DEFAULT_REGION as w}from"../../aws/utils/regions.js";import{getErrorMessage as T,maskSensitiveOutput as y}from"@fjall/util";import{CdkEventMonitor as x,resolveDeployTimeoutMs as tt,settleMonitoringAnalysis as q,startStackMonitoring as et}from"./CdkEventMonitoring.js";import{analyseDeployOutput as rt,analyseDestroyResult as ot,createEnhancedOutputCallback as nt,formatAnalysedDeployFailure as _}from"./CdkOutputAnalyser.js";import{recordDriftSuspects as at}from"../../orchestration/drift/driftSuspectJournal.js";import{CdkCommandRunner as it}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as st}from"./CdkArgumentBuilder.js";import{CdkProcessManager as ct}from"./CdkProcessManager.js";import{cancelInFlightStack as H}from"./cancelInFlightStack.js";import{awaitToolkitSettled as Y,filterBootstrapTagEntries as lt,probeToolkitStack as ut,resolveBootstrapForce as dt,MAX_BOOTSTRAP_LOST_RACE_RETRIES as z,TOOLKIT_SETTLE_BUDGET_MS as mt}from"./bootstrapConvergence.js";import{isAborted as k}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as ft}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as pt,resolveStackName as J,getFallbackStackName as gt,buildDeploymentCdkContext as M}from"./cdkServiceHelpers.js";import{ensureDependencyIntegrity as ht}from"./dependencyIntegrityGate.js";import{runInfraTypecheckPreflight as yt}from"./infraTypecheckPreflight.js";import{isCdkProcessTimeout as St,isTransientNetworkFailure as kt,isReattachableInProgressStatus as bt,MAX_TRANSIENT_NETWORK_RETRIES as K,TRANSIENT_RETRY_BACKOFF_MS as Ct}from"./transientNetworkRecovery.js";import{sleepAbortable as Dt}from"../../util/sleepAbortable.js";class Ht{static{P(this,"CdkService")}commandRunner;eventMonitor;abortSignal;constructor(t){const e=t?.processManager??new ct(new st,t?.abortSignal);this.commandRunner=new it(e),this.eventMonitor=new x({eventLogWriterFactory:t?.eventLogWriterFactory,...t?.onFailureAnalysis!==void 0?{onFailureAnalysis:t.onFailureAnalysis}:{}}),this.abortSignal=t?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(t,e,r){return this.commandRunner.checkDifferences(t,e,r)}async deploy(t,e,r){return this.commandRunner.deploy(t,e,r)}async destroy(t,e,r){return this.commandRunner.destroy(t,e,r)}async runImport(t,e,r){return this.commandRunner.runImport(t,e,r)}async synth(t,e){return this.commandRunner.synth(t,e)}async bootstrap(t,e,r){return this.commandRunner.bootstrap(t,e,r)}async runCdkSynth(t,e,r){const i=t.callerIdentity?.Account;try{const s=await ht(t.path,{...e!==void 0?{onOutput:e}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});if(!s.success)return n(s.error);const a=await yt(t.path,{...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});a.status==="failed"&&(e?.(`WARNING: TypeScript errors in ${t.path} \u2014 the synthesised template may be wrong. This check will block deploys in a future release.
2
+ ${a.details}
3
+ `),I.warn("CdkService","Infrastructure typecheck failed",{path:t.path}));const o=await this.synth(t.path,{outputCallback:e,context:M(t,i,t.region||w),...t.assemblyDir!==void 0?{outputDir:t.assemblyDir}:{},...r!==void 0?{credentials:r}:{}});return o.success?b({message:"CloudFormation template synthesised",details:o.data.output?{synthesisTime:o.data.output}:void 0}):n(o.error||"Failed to synthesise CloudFormation template")}catch(s){return n(`CDK synth failed: ${y(T(s))}`)}}async runCdkBootstrap(t,e,r,i,s){const a=t.callerIdentity?.Account,o=t.region||w;try{if(!a)return n("No AWS account ID available");const u=G(t.path,"node_modules");if(!Z(u))return n(`Dependencies not installed. Please run 'npm install' in ${t.path} before deploying.`);const d=lt(s),l=Object.keys(d).length>0,f={region:o,...r!==void 0?{credentials:r}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}};for(let p=0;p<=z;p++){if(k(this.abortSignal))return n("Bootstrap cancelled before completion");let m=await ut(f);if(m.kind==="in-progress"&&(e?.(`Another deployment is updating the CDK toolkit in ${a}/${o} \u2014 waiting for it to finish...
4
+ `),m=await Y(f)),k(this.abortSignal))return n("Bootstrap cancelled before completion");if(m.kind==="in-progress")return n(`The CDKToolkit stack in ${a}/${o} has been ${m.status} for over ${Math.round(mt/6e4)} minutes. Another deployment may be stuck bootstrapping this environment \u2014 wait for it to finish (or cancel it in the CloudFormation console), then retry.`);const h=await this.bootstrap(a,o,{outputCallback:e,credentials:r,bootstrapForce:dt(m,l?d:void 0),...i!==void 0&&i!==""?{permissionsBoundary:i}:{},...l?{bootstrapTags:d}:{}});if(h.success)return b({message:"AWS environment bootstrapped"});if(k(this.abortSignal))return n("Bootstrap cancelled before completion");if(h.error.kind==="lost-race"&&p<z){I.warn("CdkService","Concurrent bootstrap detected \u2014 waiting for it to settle before retrying",{accountId:a,region:o,attempt:p+1}),e?.(`Another deployment is bootstrapping ${a}/${o} \u2014 waiting for it to finish before retrying...
5
+ `),await Y(f);continue}return n(h.error.message||"Failed to bootstrap AWS environment")}return n("Failed to bootstrap AWS environment")}catch(u){return n(`CDK bootstrap failed: ${y(T(u))}`)}}async runCdkDiff(t,e){const r=t.callerIdentity?.Account;try{const i=await this.checkDifferences(t.path,void 0,{verbose:t.options?.verbose,outputCallback:e,context:M(t,r,t.region||w)});return i.success?b({message:"Diff check complete",details:{hasDifferences:i.data.hasDifferences,details:i.data.details}}):n(`CDK diff failed: ${i.error.message}`)}catch(i){return n(`CDK diff failed: ${y(T(i))}`)}}async runCdkDeploy(t,e,r,i,s,a,o,u){const d=t.callerIdentity?.Account,l=t.region||w;if(!d)return n("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!s)return n("AwsProvider is required for deployment monitoring.");const f=t.assemblyDir??G(t.path,"cdk.out"),p=ft(f,i);let m=null,h;try{const v=J(e,t),g=v??gt(t),S=await this.eventMonitor.createEventMonitor("deploy",g,l,t,s);m=S,r&&(r(y(`Starting CloudFormation deployment of ${g}...
6
6
  `)),r(`Monitoring CloudFormation events (CDK process running in background)...
7
- `));const l={cdkOutput:"",actualStackName:h,stackDetected:!1,monitoringPromise:null},Y=tt(l,r,f,p);y=setTimeout(()=>{!l.stackDetected&&!l.monitoringPromise&&(I.debug("CdkService","Fallback monitoring STARTING",{targetStackName:h,stackDetected:l.stackDetected,hasOnResourceProgress:!!i}),l.monitoringPromise=V(g,h,p))},ut);for(let w=0;;w++){const T=await this.deploy(t.path,h,{verbose:t.options?.verbose,outputCallback:Y,...t.assemblyDir!==void 0?{appDir:t.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:g.getEventLogger()??void 0,context:$(t,d,c),credentials:o,...n!==void 0&&Object.keys(n).length>0&&{parameters:n},...u===!0&&{exclusively:!0}});if(k(this.abortSignal))return await j(h,c,o,r),a("Deployment cancelled");const R=Z(l.cdkOutput,T,l.actualStackName);if(R.success)return await this.reconcileDeploySuccessAgainstStack({successOutput:R.data,stackName:l.actualStackName,monitor:g,monitoringPromise:l.monitoringPromise,accountId:d,region:c,onOutput:r});const q=[l.cdkOutput,T.success?T.data.output??"":T.error,R.error].join(`
8
- `);if(pt(q)){r?.(`Transient network failure while running CDK for ${l.actualStackName} \u2014 probing CloudFormation for an in-flight operation...
9
- `);const A=await g.getStackStatus(l.actualStackName,this.abortSignal);if(A!==null&&gt(A.status))return clearTimeout(y),await this.reattachToInFlightOperation({stackName:l.actualStackName,currentStatus:A.status,region:c,accountId:d,monitor:g,state:l,credentials:o,onOutput:r,onResourceProgress:p});const v=A?.status,P=v!==void 0&&W(v)&&!L(v);if(!P&&w<E){const O=ht[w]??15e3;if(r?.(`No CloudFormation operation in progress \u2014 nothing was mutated. Retrying the CDK deploy in ${O/1e3}s (retry ${w+1} of ${E})...
10
- `),await yt(O,this.abortSignal),k(this.abortSignal))return a("Deployment cancelled");l.cdkOutput="";continue}r?.(P?`CloudFormation settled at ${v} for ${l.actualStackName} \u2014 the stack was rolled back, not cleanly interrupted. Not retrying.
11
- `:`No CloudFormation operation in progress and the retry budget (${E}) is exhausted \u2014 giving up.
12
- `)}const F=await B(g,l.monitoringPromise);return F===null?R:(await this.journalDriftSuspects(d,c,l.actualStackName,F),a(N(F)))}}catch(h){const g=`CDK deploy failed: ${S(D(h))}`;return I.error("CdkService","CDK deployment exception",{error:g}),a(g)}finally{clearTimeout(y),f&&f.stopMonitoring()}}async reattachToInFlightOperation(t){const{stackName:e,monitor:r,state:i,onOutput:s}=t;s?.(`CloudFormation reports ${t.currentStatus} for ${e} \u2014 the operation survived the network failure. Re-attaching and monitoring it to completion...
13
- `),r.stopMonitoring(),i.monitoringPromise=null;const o=M(()=>{r.stopMonitoring()},"onAbort");this.abortSignal?.addEventListener("abort",o,{once:!0});const n=await r.waitForStackComplete(e,{timeout:Q,...t.onResourceProgress!==void 0?{onResourceUpdate:t.onResourceProgress}:{}}).finally(()=>{this.abortSignal?.removeEventListener("abort",o)});if(k(this.abortSignal))return await j(e,t.region,t.credentials,s),a("Deployment cancelled");if(n.success){const c=n.status??"COMPLETE";return s?.(`${e} reached ${c} after re-attach.
14
- `),b({message:`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${c}`,details:{reattached:!0,status:c}})}const u=r.getFailureAnalysis();if(u!==null)return await this.journalDriftSuspects(t.accountId,t.region,e,u),a(N(u));const d=n.failureReason!==void 0&&n.failureReason!==""?S(n.failureReason):"no failure reason reported";return a(`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${n.status??"UNKNOWN"}: ${d}`)}async reconcileDeploySuccessAgainstStack(t){const{successOutput:e,stackName:r,monitor:i,onOutput:s}=t;if(e.status==="NO_CHANGES"||e.status==="SKIPPED"||e.details?.skipped===!0)return b(e);const o=await i.getStackStatus(r,this.abortSignal);if(o===null||!W(o.status)||L(o.status))return b(e);s?.(`CDK reported success but CloudFormation settled at ${o.status} for ${r} \u2014 the deployment did not converge. Reporting failure.
15
- `);const n=await B(i,t.monitoringPromise);if(n!==null)return await this.journalDriftSuspects(t.accountId,t.region,r,n),a(N(n));const u=o.statusReason!==void 0&&o.statusReason!==""?S(o.statusReason):"no failure reason reported";return a(`Deployment of ${r} reported success but CloudFormation settled at ${o.status}: ${u}`)}async journalDriftSuspects(t,e,r,i){i.driftSuspects===void 0||i.driftSuspects.length===0||await et({accountId:t,region:e,stackName:r,recordedAt:new Date().toISOString(),suspects:i.driftSuspects.map(s=>({stackName:r,logicalId:s.logicalId,resourceType:s.resourceType,...s.physicalId!==void 0?{physicalId:s.physicalId}:{},failedStatus:s.failedStatus,statusReason:s.statusReason}))})}async runCdkDestroy(t,e,r,i,s,o,n){const u=t.callerIdentity?.Account,d=t.region||C;let c=null;try{const m=X(e,t);u&&m&&s&&(c=await this.eventMonitor.createEventMonitor("destroy",m,d,t,s),c.startMonitoring(m,f=>{i?.(f)},(f,y)=>{}));const p=await this.destroy(t.path,e,{verbose:t.options?.verbose,outputCallback:r,useCdkOut:o,cdkOutputLogger:c?.getEventLogger()??void 0,context:$(t,u,d),credentials:n});return k(this.abortSignal)?a("Destroy cancelled"):x(p)}catch(m){return a(`CDK destroy failed: ${S(D(m))}`)}finally{c&&c.stopMonitoring()}}}export{jt as CdkService};
7
+ `));const c={cdkOutput:"",actualStackName:g,stackNameIsAuthoritative:v!==void 0,stackDetected:!1,monitoringPromise:null},Q=nt(c,r,m,p);h=setTimeout(()=>{!c.stackDetected&&!c.monitoringPromise&&(I.debug("CdkService","Fallback monitoring STARTING",{targetStackName:g,stackDetected:c.stackDetected,hasOnResourceProgress:!!i}),c.monitoringPromise=et(S,g,p))},pt);const L=t.options?.deployTimeoutMs??tt(),W=Date.now();for(let R=0;;R++){const A=await this.deploy(t.path,g,{verbose:t.options?.verbose,timeout:Math.max(1,L-(Date.now()-W)),outputCallback:Q,...t.assemblyDir!==void 0?{appDir:t.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:S.getEventLogger()??void 0,context:M(t,d,l),credentials:a,...o!==void 0&&Object.keys(o).length>0&&{parameters:o},...u===!0&&{exclusively:!0}});if(k(this.abortSignal))return await H(g,l,a,r),n("Deployment cancelled");const $=rt(c.cdkOutput,A,c.actualStackName);if($.success)return await this.reconcileDeploySuccessAgainstStack({successOutput:$.data,stackName:c.actualStackName,monitor:S,monitoringPromise:c.monitoringPromise,accountId:d,region:l,onOutput:r});const B=[c.cdkOutput,A.success?A.data.output??"":A.error,$.error].join(`
8
+ `),N=St(B);if(kt(B)||N){r?.(N?`The CDK child for ${c.actualStackName} outlived its timeout and was killed \u2014 that does not stop the CloudFormation operation. Probing for an in-flight operation...
9
+ `:`Transient network failure while running CDK for ${c.actualStackName} \u2014 probing CloudFormation for an in-flight operation...
10
+ `);const C=await S.getStackStatus(c.actualStackName,this.abortSignal);if(C!==null&&bt(C.status))return clearTimeout(h),await this.reattachToInFlightOperation({stackName:c.actualStackName,currentStatus:C.status,region:l,accountId:d,monitor:S,state:c,credentials:a,remainingTimeoutMs:Math.max(0,L-(Date.now()-W)),onOutput:r,onResourceProgress:p});if(N){const D=C?.status;return n(`CDK deploy for ${c.actualStackName} exceeded its timeout`+(D!==void 0?`; CloudFormation reports ${D}`:" and CloudFormation reports no operation in progress")+". Raise the deploy timeout rather than re-running \u2014 the stack was not interrupted by an error.")}const F=C?.status,j=F!==void 0&&X(F)&&!U(F);if(!j&&R<K){const D=Ct[R]??15e3;if(r?.(`No CloudFormation operation in progress \u2014 nothing was mutated. Retrying the CDK deploy in ${D/1e3}s (retry ${R+1} of ${K})...
11
+ `),await Dt(D,this.abortSignal),k(this.abortSignal))return n("Deployment cancelled");c.cdkOutput="";continue}r?.(j?`CloudFormation settled at ${F} for ${c.actualStackName} \u2014 the stack was rolled back, not cleanly interrupted. Not retrying.
12
+ `:`No CloudFormation operation in progress and the retry budget (${K}) is exhausted \u2014 giving up.
13
+ `)}const E=await q(S,c.monitoringPromise);return E===null?$:(await this.journalDriftSuspects(d,l,c.actualStackName,E),n(_(E)))}}catch(v){const g=`CDK deploy failed: ${y(T(v))}`;return I.error("CdkService","CDK deployment exception",{error:g}),n(g)}finally{clearTimeout(h),m&&m.stopMonitoring()}}async reattachToInFlightOperation(t){const{stackName:e,monitor:r,state:i,onOutput:s}=t;s?.(`CloudFormation reports ${t.currentStatus} for ${e} \u2014 the operation survived the network failure. Re-attaching and monitoring it to completion...
14
+ `),r.stopMonitoring(),i.monitoringPromise=null;const a=P(()=>{r.stopMonitoring()},"onAbort");this.abortSignal?.addEventListener("abort",a,{once:!0});const o=await r.waitForStackComplete(e,{timeout:t.remainingTimeoutMs,...t.onResourceProgress!==void 0?{onResourceUpdate:t.onResourceProgress}:{}}).finally(()=>{this.abortSignal?.removeEventListener("abort",a)});if(k(this.abortSignal))return await H(e,t.region,t.credentials,s),n("Deployment cancelled");if(o.success){const l=o.status??"COMPLETE";return s?.(`${e} reached ${l} after re-attach.
15
+ `),b({message:`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${l}`,details:{reattached:!0,status:l}})}const u=r.getFailureAnalysis();if(u!==null)return await this.journalDriftSuspects(t.accountId,t.region,e,u),n(_(u));const d=o.failureReason!==void 0&&o.failureReason!==""?y(o.failureReason):"no failure reason reported";return n(`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${o.status??"UNKNOWN"}: ${d}`)}async reconcileDeploySuccessAgainstStack(t){const{successOutput:e,stackName:r,monitor:i,onOutput:s}=t;if(e.status==="NO_CHANGES"||e.status==="SKIPPED"||e.details?.skipped===!0)return b(e);const a=await i.getStackStatus(r,this.abortSignal);if(a===null||!X(a.status)||U(a.status))return b(e);s?.(`CDK reported success but CloudFormation settled at ${a.status} for ${r} \u2014 the deployment did not converge. Reporting failure.
16
+ `);const o=await q(i,t.monitoringPromise);if(o!==null)return await this.journalDriftSuspects(t.accountId,t.region,r,o),n(_(o));const u=a.statusReason!==void 0&&a.statusReason!==""?y(a.statusReason):"no failure reason reported";return n(`Deployment of ${r} reported success but CloudFormation settled at ${a.status}: ${u}`)}async journalDriftSuspects(t,e,r,i){i.driftSuspects===void 0||i.driftSuspects.length===0||await at({accountId:t,region:e,stackName:r,recordedAt:new Date().toISOString(),suspects:i.driftSuspects.map(s=>({stackName:r,logicalId:s.logicalId,resourceType:s.resourceType,...s.physicalId!==void 0?{physicalId:s.physicalId}:{},failedStatus:s.failedStatus,statusReason:s.statusReason}))})}async runCdkDestroy(t,e,r,i,s,a,o){const u=t.callerIdentity?.Account,d=t.region||w;let l=null;try{const f=J(e,t);u&&f&&s&&(l=await this.eventMonitor.createEventMonitor("destroy",f,d,t,s),l.startMonitoring(f,m=>{i?.(m)},(m,h)=>{}));const p=await this.destroy(t.path,e,{verbose:t.options?.verbose,outputCallback:r,useCdkOut:a,cdkOutputLogger:l?.getEventLogger()??void 0,context:M(t,u,d),credentials:o});return k(this.abortSignal)?n("Destroy cancelled"):ot(p)}catch(f){return n(`CDK destroy failed: ${y(T(f))}`)}finally{l&&l.stopMonitoring()}}}export{Ht as CdkService};
@@ -46,30 +46,23 @@ export declare class EcsServiceResolver {
46
46
  private cfService;
47
47
  constructor(cfService: CfnExportsClient);
48
48
  /**
49
- * Resolve cluster + service ARNs by exact CloudFormation export names
50
- * derived from manifest data. Avoids substring matching that can cause
51
- * cross-app collisions when multiple apps share an account.
52
- */
53
- getDeployableClusterAndServicesByManifest(manifestData: {
54
- clusters: Array<{
55
- name: string;
56
- services: Array<{
57
- name: string;
58
- }>;
59
- }>;
60
- }, abortSignal?: AbortSignal): Promise<Result<{
61
- clusterArn?: string;
62
- serviceArns: string[];
63
- }, EcsError>>;
64
- /**
65
- * Fallback resolution by app name using token-boundary matching on
66
- * CloudFormation exports (see `matchesAppNameToken`). Prefer the
67
- * manifest-based variant when manifest data is available.
49
+ * Resolve cluster + service ARNs by app name, using token-boundary matching
50
+ * on CloudFormation exports (see `matchesAppNameToken`).
68
51
  *
69
52
  * Export naming convention from infrastructure:
70
- * Cluster: `${clusterName}DeployableCluster`
71
- * Service: `${clusterName}${serviceName}DeployableService`
72
- * where clusterName typically contains the app name (e.g. "StandardsapiCompute").
53
+ * Cluster: `${stackName}${clusterName}DeployableCluster`
54
+ * Service: `${stackName}${clusterName}${serviceName}DeployableService`
55
+ *
56
+ * Matching by suffix + app-name token rather than by whole name is
57
+ * deliberate, and it is safe because of the `${stackName}` prefix: Fjall
58
+ * derives stack names from the app name, so the token is always present.
59
+ * It used to be only *typically* present (it depended on the app happening
60
+ * to name its cluster after itself), which is why an exact-name variant
61
+ * existed alongside this one — `stackScopedExportName` in
62
+ * @fjall/components-infrastructure removed the need for it.
63
+ *
64
+ * Do not match on the whole export name here. The prefix is a construct of
65
+ * the emitting stack, not something a caller can reconstruct.
73
66
  */
74
67
  getDeployableClusterAndServices(appName: string, abortSignal?: AbortSignal): Promise<Result<{
75
68
  clusterArn?: string;
@@ -1 +1 @@
1
- var y=Object.defineProperty;var f=(i,o)=>y(i,"name",{value:o,configurable:!0});import{success as a}from"@fjall/generator";import{logger as d}from"@fjall/util/logger";import{getErrorMessage as g,maskSensitiveOutput as m}from"@fjall/util";import{extractEcsServiceParts as S,buildArn as x}from"../../aws/utils/arnParser.js";function h(i,o){const u=i.toLowerCase(),r=o.toLowerCase();if(r.length===0)return!1;const l=f(e=>!/[a-zA-Z0-9]/.test(e),"isSeparator"),n=f(e=>/[A-Z]/.test(e),"isUpper");let s=u.indexOf(r);for(;s!==-1;){const e=i[s]??"",t=s>0?i[s-1]??"":"",c=s===0||n(e)||l(t),v=s+r.length,p=i[v],b=p===void 0||n(p)||l(p);if(c&&b)return!0;s=u.indexOf(r,s+1)}return!1}f(h,"matchesAppNameToken");class R{static{f(this,"EcsServiceResolver")}cfService;constructor(o){this.cfService=o}async getDeployableClusterAndServicesByManifest(o,u){try{const r=[];for(const t of o.clusters){r.push(`${t.name}DeployableCluster`);for(const c of t.services)r.push(`${t.name}${c.name}DeployableService`)}if(r.length===0)return a({serviceArns:[]});const l=await this.cfService.getExportsByNames(r,u);if(!l.success)return d.debug("EcsServiceResolver","Failed to get CloudFormation exports by name",{error:l.error.message}),a({serviceArns:[]});const n=l.data;let s;for(const t of o.clusters){const c=n.get(`${t.name}DeployableCluster`);if(c){s=c;break}}const e=[];for(const t of o.clusters)for(const c of t.services){const v=n.get(`${t.name}${c.name}DeployableService`);v&&e.push(v)}return d.debug("EcsServiceResolver",`Manifest-based discovery: found ${e.length} service(s), cluster=${s||"not found"}`),a({clusterArn:s,serviceArns:e})}catch(r){return d.warn("EcsServiceResolver","Manifest-based discovery failed",{error:m(g(r))}),a({serviceArns:[]})}}async getDeployableClusterAndServices(o,u){try{const r=await this.cfService.listExports(void 0,u);if(!r.success)return d.debug("EcsServiceResolver","Failed to list CloudFormation exports",{error:r.error.message}),a({serviceArns:[]});const n=r.data.filter(e=>{const t=e.Name??"";return h(t,o)&&t.toLowerCase().endsWith("deployableservice")}).map(e=>e.Value).filter(e=>e!==void 0);let s;if(n.length>0){const e=S(n[0]);e&&(s=x("ecs",e.region,e.accountId,`cluster/${e.clusterName}`))}return s||(s=r.data.find(t=>{const c=t.Name??"";return h(c,o)&&c.toLowerCase().endsWith("deployablecluster")})?.Value),d.debug("EcsServiceResolver",`Found ${n.length} deployable service(s) for app "${o}" in cluster ${s||"unknown"}`),a({clusterArn:s,serviceArns:n})}catch(r){return d.warn("EcsServiceResolver","Failed to get deployable services",{error:m(g(r))}),a({serviceArns:[]})}}}export{R as EcsServiceResolver,h as matchesAppNameToken};
1
+ var m=Object.defineProperty;var i=(o,s)=>m(o,"name",{value:s,configurable:!0});import{success as f}from"@fjall/generator";import{logger as v}from"@fjall/util/logger";import{getErrorMessage as E,maskSensitiveOutput as x}from"@fjall/util";import{extractEcsServiceParts as S,buildArn as b}from"../../aws/utils/arnParser.js";function p(o,s){const l=o.toLowerCase(),t=s.toLowerCase();if(t.length===0)return!1;const u=i(e=>!/[a-zA-Z0-9]/.test(e),"isSeparator"),n=i(e=>/[A-Z]/.test(e),"isUpper");let r=l.indexOf(t);for(;r!==-1;){const e=o[r]??"",c=r>0?o[r-1]??"":"",a=r===0||n(e)||u(c),h=r+t.length,d=o[h],g=d===void 0||n(d)||u(d);if(a&&g)return!0;r=l.indexOf(t,r+1)}return!1}i(p,"matchesAppNameToken");class R{static{i(this,"EcsServiceResolver")}cfService;constructor(s){this.cfService=s}async getDeployableClusterAndServices(s,l){try{const t=await this.cfService.listExports(void 0,l);if(!t.success)return v.debug("EcsServiceResolver","Failed to list CloudFormation exports",{error:t.error.message}),f({serviceArns:[]});const n=t.data.filter(e=>{const c=e.Name??"";return p(c,s)&&c.toLowerCase().endsWith("deployableservice")}).map(e=>e.Value).filter(e=>e!==void 0);let r;if(n.length>0){const e=S(n[0]);e&&(r=b("ecs",e.region,e.accountId,`cluster/${e.clusterName}`))}return r||(r=t.data.find(c=>{const a=c.Name??"";return p(a,s)&&a.toLowerCase().endsWith("deployablecluster")})?.Value),v.debug("EcsServiceResolver",`Found ${n.length} deployable service(s) for app "${s}" in cluster ${r||"unknown"}`),f({clusterArn:r,serviceArns:n})}catch(t){return v.warn("EcsServiceResolver","Failed to get deployable services",{error:x(E(t))}),f({serviceArns:[]})}}}export{R as EcsServiceResolver,p as matchesAppNameToken};
@@ -17,6 +17,20 @@
17
17
  */
18
18
  /** True when the failure text carries a transient network signature. */
19
19
  export declare function isTransientNetworkFailure(text: string): boolean;
20
+ /**
21
+ * True when the deploy child was killed by our own spawn timeout.
22
+ *
23
+ * Same predicament as a network kill, different cause: the child is dead and
24
+ * the CloudFormation operation it started is not. It therefore warrants the
25
+ * same CFN probe — but NOT the same retry. A transient failure retries
26
+ * because the premise "no operation in flight ⇒ nothing was mutated" makes a
27
+ * re-spawn safe; a timeout carries no such premise. The deploy was running
28
+ * fine and simply outlived its budget, so re-running it buys another timeout.
29
+ *
30
+ * Anchored on the exact string `CdkProcessManager` resolves — a substring of
31
+ * customer stack output cannot forge it.
32
+ */
33
+ export declare function isCdkProcessTimeout(text: string): boolean;
20
34
  /**
21
35
  * True when a stack status names a running operation worth re-attaching to.
22
36
  * REVIEW_IN_PROGRESS is excluded: it is a pre-execution changeset
@@ -1 +1 @@
1
- var E=Object.defineProperty;var n=(e,t)=>E(e,"name",{value:t,configurable:!0});const o=[/\bECONNRESET\b/,/\bETIMEDOUT\b/,/\bEAI_AGAIN\b/,/\bENOTFOUND\b/,/connection reset/i,/socket hang ?up/i,/getaddrinfo/i,/inaccessible host/i,/NetworkingError/,/TLS handshake/i];function N(e){return o.some(t=>t.test(e))}n(N,"isTransientNetworkFailure");function T(e){return e.endsWith("_IN_PROGRESS")&&e!=="REVIEW_IN_PROGRESS"}n(T,"isReattachableInProgressStatus");const i=2,R=[5e3,15e3];export{i as MAX_TRANSIENT_NETWORK_RETRIES,R as TRANSIENT_RETRY_BACKOFF_MS,T as isReattachableInProgressStatus,N as isTransientNetworkFailure};
1
+ var o=Object.defineProperty;var e=(t,n)=>o(t,"name",{value:n,configurable:!0});const r=[/\bECONNRESET\b/,/\bETIMEDOUT\b/,/\bEAI_AGAIN\b/,/\bENOTFOUND\b/,/connection reset/i,/socket hang ?up/i,/getaddrinfo/i,/inaccessible host/i,/NetworkingError/,/TLS handshake/i];function E(t){return r.some(n=>n.test(t))}e(E,"isTransientNetworkFailure");function s(t){return/CDK command timed out/.test(t)}e(s,"isCdkProcessTimeout");function T(t){return t.endsWith("_IN_PROGRESS")&&t!=="REVIEW_IN_PROGRESS"}e(T,"isReattachableInProgressStatus");const N=2,R=[5e3,15e3];export{N as MAX_TRANSIENT_NETWORK_RETRIES,R as TRANSIENT_RETRY_BACKOFF_MS,s as isCdkProcessTimeout,T as isReattachableInProgressStatus,E as isTransientNetworkFailure};
@@ -33,6 +33,15 @@ export interface DeploymentContext {
33
33
  options: {
34
34
  verbose?: boolean;
35
35
  infraOnly?: boolean;
36
+ /**
37
+ * Total wall-clock budget for the monitored deploy, shared by the CDK
38
+ * child and any re-attach that follows it being killed. Absent ⇒
39
+ * `resolveDeployTimeoutMs()` (the `FJALL_DEPLOY_TIMEOUT_MS` env var, else
40
+ * 30 minutes). Set this when the caller knows the stack's real duration —
41
+ * a global Aurora cluster or a CloudFront distribution outruns the
42
+ * default without anything being wrong.
43
+ */
44
+ deployTimeoutMs?: number;
36
45
  };
37
46
  path: string;
38
47
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/deploy-core",
3
- "version": "7.3.0",
3
+ "version": "8.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -95,15 +95,15 @@
95
95
  "@aws-sdk/client-ssm": "^3.1098.0",
96
96
  "@aws-sdk/client-sso-admin": "^3.1098.0",
97
97
  "@aws-sdk/client-sts": "^3.1098.0",
98
- "@fjall/generator": "^7.3.0",
99
- "@fjall/util": "^7.3.0",
98
+ "@fjall/generator": "^8.0.0",
99
+ "@fjall/util": "^8.0.0",
100
100
  "@smithy/node-http-handler": "^4.9.13",
101
101
  "aws-cdk": "^2.1134.0",
102
102
  "tsx": "^4.23.1",
103
103
  "zod": "^4.4.3"
104
104
  },
105
105
  "peerDependencies": {
106
- "@fjall/components-infrastructure": ">=7.0.0 <8.0.0"
106
+ "@fjall/components-infrastructure": ">=8.0.0 <9.0.0"
107
107
  },
108
108
  "peerDependenciesMeta": {
109
109
  "@fjall/components-infrastructure": {