@fjall/deploy-core 2.33.0 → 3.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.
Files changed (210) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/SimpleAwsProvider.js +1 -1
  3. package/dist/src/aws/cloudtrail/orgTrailDelivery.js +1 -1
  4. package/dist/src/aws/organisations/accountGlobals.js +1 -1
  5. package/dist/src/aws/organisations/accounts.js +1 -1
  6. package/dist/src/aws/organisations/backup.js +2 -2
  7. package/dist/src/aws/organisations/costAllocation.js +1 -1
  8. package/dist/src/aws/organisations/delegatedAdmin.js +2 -2
  9. package/dist/src/aws/organisations/identityCentre.js +1 -1
  10. package/dist/src/aws/organisations/identityCentreSnapshot.js +1 -1
  11. package/dist/src/aws/organisations/importedAccounts.js +1 -1
  12. package/dist/src/aws/organisations/ipam.js +1 -1
  13. package/dist/src/aws/organisations/oidcProvider.js +1 -1
  14. package/dist/src/aws/organisations/organisation.js +1 -1
  15. package/dist/src/aws/organisations/organisationalUnits.js +1 -1
  16. package/dist/src/aws/organisations/pagination.js +1 -1
  17. package/dist/src/aws/organisations/policies.js +1 -1
  18. package/dist/src/aws/organisations/ram.js +1 -1
  19. package/dist/src/aws/organisations/rootAccess.js +3 -3
  20. package/dist/src/aws/organisations/serviceAccess.js +1 -1
  21. package/dist/src/aws/organisations/trustedAccess.js +1 -1
  22. package/dist/src/aws/organisations/types.js +1 -1
  23. package/dist/src/aws/regions/regionOptStatus.js +1 -1
  24. package/dist/src/aws/sts/assumeRoot.js +1 -1
  25. package/dist/src/aws/targetReadiness.js +1 -1
  26. package/dist/src/aws/targetSetAdvisory.js +1 -1
  27. package/dist/src/aws/utils/arnParser.js +1 -1
  28. package/dist/src/aws/utils/awsErrorHandler.js +1 -1
  29. package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -1
  30. package/dist/src/aws/utils/cloudformationEvents.js +1 -1
  31. package/dist/src/index.d.ts +4 -2
  32. package/dist/src/index.js +1 -1
  33. package/dist/src/orchestration/activeDeploymentGuard.js +5 -5
  34. package/dist/src/orchestration/application/applicationDeploy.js +1 -1
  35. package/dist/src/orchestration/application/applicationDeployHelpers.js +3 -3
  36. package/dist/src/orchestration/application/applicationDestroy.js +1 -1
  37. package/dist/src/orchestration/application/approvalGate.js +1 -1
  38. package/dist/src/orchestration/application/artefactCollection.js +1 -1
  39. package/dist/src/orchestration/application/buildArgResolver.js +1 -1
  40. package/dist/src/orchestration/application/buildGroupValidator.js +1 -1
  41. package/dist/src/orchestration/application/buildSecretResolver.js +1 -1
  42. package/dist/src/orchestration/application/buildSecretSession.js +1 -1
  43. package/dist/src/orchestration/application/codeOnlyDeploy.js +1 -1
  44. package/dist/src/orchestration/application/databaseEndpointReconcile.js +1 -1
  45. package/dist/src/orchestration/application/deploySessionPolicy.js +1 -1
  46. package/dist/src/orchestration/application/destroyPreFlight.js +1 -1
  47. package/dist/src/orchestration/application/destructionGate.js +3 -3
  48. package/dist/src/orchestration/application/detectionPipeline.js +1 -1
  49. package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
  50. package/dist/src/orchestration/application/ecrCacheRepository.js +1 -1
  51. package/dist/src/orchestration/application/lambdaCodeOnlyRollout.js +1 -1
  52. package/dist/src/orchestration/application/leaseGate.js +1 -1
  53. package/dist/src/orchestration/application/openNextBuild.js +3 -3
  54. package/dist/src/orchestration/application/plan/approvalToken.js +1 -1
  55. package/dist/src/orchestration/application/plan/assemblyDigest.js +2 -2
  56. package/dist/src/orchestration/application/plan/buildDeployPlan.js +1 -1
  57. package/dist/src/orchestration/application/plan/changeSetEscalation.js +1 -1
  58. package/dist/src/orchestration/application/plan/classify.js +1 -1
  59. package/dist/src/orchestration/application/plan/computeDeployPlan.js +1 -1
  60. package/dist/src/orchestration/application/plan/loadDeployPlan.js +1 -1
  61. package/dist/src/orchestration/application/plan/pinnedNames.js +1 -1
  62. package/dist/src/orchestration/application/plan/registryOverlay.js +1 -1
  63. package/dist/src/orchestration/application/plan/renderDeployPlan.js +1 -1
  64. package/dist/src/orchestration/application/serviceImageTagsRollback.js +1 -1
  65. package/dist/src/orchestration/application/taskDefinitionRoll.js +1 -1
  66. package/dist/src/orchestration/application/welcomeImageHelper.js +1 -1
  67. package/dist/src/orchestration/builders/dockerBuilder.js +1 -1
  68. package/dist/src/orchestration/builders/frameworkRegistry.js +1 -1
  69. package/dist/src/orchestration/builders/openNextBuilder.js +1 -1
  70. package/dist/src/orchestration/builders/staticSiteBuilder.js +2 -2
  71. package/dist/src/orchestration/contextHelpers.js +1 -1
  72. package/dist/src/orchestration/deploy.js +1 -1
  73. package/dist/src/orchestration/destroy.js +1 -1
  74. package/dist/src/orchestration/dockerBuildParamsKeys.js +1 -1
  75. package/dist/src/orchestration/dockerInterface.d.ts +16 -0
  76. package/dist/src/orchestration/domain/caaPreflight.d.ts +56 -0
  77. package/dist/src/orchestration/domain/caaPreflight.js +1 -0
  78. package/dist/src/orchestration/domain/delegatedDomainDeploy.d.ts +79 -0
  79. package/dist/src/orchestration/domain/delegatedDomainDeploy.js +1 -0
  80. package/dist/src/orchestration/domain/delegationDestroyMirror.d.ts +141 -0
  81. package/dist/src/orchestration/domain/delegationDestroyMirror.js +1 -0
  82. package/dist/src/orchestration/domain/index.d.ts +10 -0
  83. package/dist/src/orchestration/domain/index.js +1 -0
  84. package/dist/src/orchestration/domain/nsPropagationGate.d.ts +59 -0
  85. package/dist/src/orchestration/domain/nsPropagationGate.js +1 -0
  86. package/dist/src/orchestration/domain/types.d.ts +75 -0
  87. package/dist/src/orchestration/domain/types.js +1 -0
  88. package/dist/src/orchestration/domainInterface.d.ts +10 -0
  89. package/dist/src/orchestration/drift/classifyDriftFailure.js +1 -1
  90. package/dist/src/orchestration/drift/detectStackDrift.js +1 -1
  91. package/dist/src/orchestration/drift/driftPreFlight.js +5 -5
  92. package/dist/src/orchestration/drift/driftProbe.d.ts +12 -6
  93. package/dist/src/orchestration/drift/driftProbe.js +1 -1
  94. package/dist/src/orchestration/drift/driftSuspectJournal.js +1 -1
  95. package/dist/src/orchestration/drift/preFlightGate.d.ts +10 -2
  96. package/dist/src/orchestration/drift/preFlightGate.js +2 -2
  97. package/dist/src/orchestration/drift/route53Names.d.ts +35 -0
  98. package/dist/src/orchestration/drift/route53Names.js +1 -0
  99. package/dist/src/orchestration/drift/route53RecordPreflight.d.ts +105 -0
  100. package/dist/src/orchestration/drift/route53RecordPreflight.js +2 -0
  101. package/dist/src/orchestration/index.d.ts +3 -1
  102. package/dist/src/orchestration/index.js +1 -1
  103. package/dist/src/orchestration/manifestSecretParser.js +1 -1
  104. package/dist/src/orchestration/organisation/accountsConfig.js +1 -1
  105. package/dist/src/orchestration/organisation/cascadeDestroyHelpers.js +1 -1
  106. package/dist/src/orchestration/organisation/cascadeHelpers.d.ts +18 -1
  107. package/dist/src/orchestration/organisation/cascadeHelpers.js +1 -1
  108. package/dist/src/orchestration/organisation/cascadeSummary.js +1 -1
  109. package/dist/src/orchestration/organisation/organisationDeploy.js +1 -1
  110. package/dist/src/orchestration/organisation/organisationDestroy.js +2 -2
  111. package/dist/src/orchestration/organisation/organisationSetup.js +1 -1
  112. package/dist/src/orchestration/organisation/reconcileProviderAccounts.js +1 -1
  113. package/dist/src/orchestration/organisationDeploy/cascadeExecution.js +1 -1
  114. package/dist/src/orchestration/organisationDeploy/infraSteps.js +1 -1
  115. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +3 -3
  116. package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -1
  117. package/dist/src/orchestration/organisationDeploy/resolveCascadeAccounts.js +1 -1
  118. package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -1
  119. package/dist/src/orchestration/organisationDeploy/trailReconciliation.js +1 -1
  120. package/dist/src/orchestration/probes/deleteBlockProbes.js +1 -1
  121. package/dist/src/orchestration/remediation/driftRepairGate.js +1 -1
  122. package/dist/src/orchestration/remediation/forensicsCapture.js +1 -1
  123. package/dist/src/orchestration/remediation/forgetResource.js +1 -1
  124. package/dist/src/orchestration/remediation/pinRemediation.js +1 -1
  125. package/dist/src/orchestration/remediation/postFailureRepairOffer.js +8 -8
  126. package/dist/src/orchestration/remediation/recreatePreFlight.js +1 -1
  127. package/dist/src/orchestration/remediation/recreateResource.js +1 -1
  128. package/dist/src/orchestration/remediation/remediationJournal.js +2 -2
  129. package/dist/src/orchestration/remediation/removalUpdate.js +1 -1
  130. package/dist/src/orchestration/remediation/retainFlip.js +1 -1
  131. package/dist/src/orchestration/resolveOperation.js +1 -1
  132. package/dist/src/orchestration/restart/restartApplication.js +1 -1
  133. package/dist/src/orchestration/restart/secretsDrift.js +1 -1
  134. package/dist/src/orchestration/secretArnResolver.js +1 -1
  135. package/dist/src/orchestration/serviceFactory.js +1 -1
  136. package/dist/src/orchestration/spawnHelpers.js +1 -1
  137. package/dist/src/orchestration/stackCleanup/bucketOps.js +1 -1
  138. package/dist/src/orchestration/stackCleanup/failedStack.js +1 -1
  139. package/dist/src/orchestration/stackCleanup/logging.js +1 -1
  140. package/dist/src/orchestration/stackCleanup/messages.js +1 -1
  141. package/dist/src/orchestration/stackCleanup/orphanSweep.js +1 -1
  142. package/dist/src/orchestration/stackCleanup/preEmpty.js +1 -1
  143. package/dist/src/orchestration/stackCleanup/stackResources.js +1 -1
  144. package/dist/src/orchestration/stepLifecycle.js +1 -1
  145. package/dist/src/orchestration/trailMigration/memberTrailCleanup.js +1 -1
  146. package/dist/src/orchestration/trailMigration/trailMigration.js +1 -1
  147. package/dist/src/orchestration/unlock/bucketPolicyTriage.js +1 -1
  148. package/dist/src/orchestration/unlock/restoreAndReconcileQuarantinedBucket.js +1 -1
  149. package/dist/src/orchestration/unlock/restoreBucketPolicy.js +1 -1
  150. package/dist/src/orchestration/unlock/scpRemediation.js +1 -1
  151. package/dist/src/orchestration/unlock/toResourcePolicyStatements.js +1 -1
  152. package/dist/src/orchestration/unlock/unlockBucket.js +1 -1
  153. package/dist/src/orchestration/unlock/unlockQueue.js +1 -1
  154. package/dist/src/services/application/ApplicationStackService.d.ts +7 -0
  155. package/dist/src/services/application/ApplicationStackService.js +1 -1
  156. package/dist/src/services/application/applicationStackHelpers.js +3 -3
  157. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -1
  158. package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -2
  159. package/dist/src/services/infrastructure/CdkErrorFormatter.js +10 -10
  160. package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -1
  161. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +2 -2
  162. package/dist/src/services/infrastructure/CdkOutputParser.js +2 -2
  163. package/dist/src/services/infrastructure/CdkProcessManager.js +3 -3
  164. package/dist/src/services/infrastructure/CdkService.d.ts +1 -1
  165. package/dist/src/services/infrastructure/CdkService.js +2 -2
  166. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +39 -0
  167. package/dist/src/services/infrastructure/CfnRegistryService.js +1 -1
  168. package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
  169. package/dist/src/services/infrastructure/EcrImageInspectorService.js +1 -1
  170. package/dist/src/services/infrastructure/EcsService.js +1 -1
  171. package/dist/src/services/infrastructure/EcsServiceResolver.js +1 -1
  172. package/dist/src/services/infrastructure/LambdaService.js +1 -1
  173. package/dist/src/services/infrastructure/SsmParameterMetadataService.js +1 -1
  174. package/dist/src/services/infrastructure/cancelInFlightStack.js +3 -3
  175. package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -1
  176. package/dist/src/services/infrastructure/changeSetProbe.js +1 -1
  177. package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -1
  178. package/dist/src/services/supporting/CdkContextBuilder.d.ts +1 -0
  179. package/dist/src/services/supporting/CdkContextBuilder.js +1 -1
  180. package/dist/src/services/supporting/TemplateHashService.js +1 -1
  181. package/dist/src/services/supporting/helpers.js +1 -1
  182. package/dist/src/steps/stepRegistry.js +1 -1
  183. package/dist/src/types/ProgressEvent.js +1 -1
  184. package/dist/src/types/application/ApplicationServiceTypes.js +1 -1
  185. package/dist/src/types/callbacks.js +1 -1
  186. package/dist/src/types/config/FjallState.js +1 -1
  187. package/dist/src/types/constants.js +1 -1
  188. package/dist/src/types/deployEmitter.js +1 -1
  189. package/dist/src/types/deployEvent.d.ts +1 -0
  190. package/dist/src/types/deployEvent.js +1 -1
  191. package/dist/src/types/deployEventParse.js +1 -1
  192. package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
  193. package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
  194. package/dist/src/types/deploymentEventSchema.d.ts +1 -0
  195. package/dist/src/types/deploymentEventSchema.js +1 -1
  196. package/dist/src/types/detection/patternDetection.js +1 -1
  197. package/dist/src/types/detection/patternTypes.d.ts +9 -0
  198. package/dist/src/types/drift.js +1 -1
  199. package/dist/src/types/errors/CdkError.js +1 -1
  200. package/dist/src/types/errors/ServiceError.js +1 -1
  201. package/dist/src/types/operations.d.ts +26 -2
  202. package/dist/src/types/operations.js +1 -1
  203. package/dist/src/types/params.d.ts +20 -0
  204. package/dist/src/types/remediation.js +1 -1
  205. package/dist/src/types/stepDefinitions.d.ts +3 -0
  206. package/dist/src/types/stepDefinitions.js +1 -1
  207. package/dist/src/util/dockerfileDetection.js +1 -1
  208. package/dist/src/util/sequencedCallbacks.js +1 -1
  209. package/dist/src/util/sleepAbortable.js +1 -1
  210. package/package.json +5 -4
@@ -1 +1 @@
1
- import{PutBucketPolicyCommand as u}from"@aws-sdk/client-s3";import{success as a,failure as o}from"@fjall/generator";import{getErrorMessage as m,maskSensitiveOutput as f}from"@fjall/util";import{composeSdkAbortSignal as l,extractErrorName as S,isAccessDenied as p}from"../../aws/organisations/types.js";import{isEnforceSslStatement as d}from"./bucketPolicyTriage.js";const y="2012-10-17";async function A(e,t){const{bucketName:r,policyDocument:i,abortSignal:c}=t;try{await e.send(new u({Bucket:r,Policy:i}),{abortSignal:l(c)})}catch(n){return p(S(n))?o({message:`PutBucketPolicy was denied on ${r}.`,isAccessDenied:!0}):o({message:`Failed to restore the bucket policy on ${r}: ${f(m(n))}`,isAccessDenied:!1})}return a({outcome:"restored"})}function s(e){const t=`arn:aws:s3:::${e}`;return{Sid:"DenyInsecureTransport",Effect:"Deny",Principal:{AWS:"*"},Action:"s3:*",Resource:[t,`${t}/*`],Condition:{Bool:{"aws:SecureTransport":"false"}}}}function B(e){return JSON.stringify({Version:y,Statement:[s(e)]})}function w(e,t){return e.Statement.some(d)?e:{...e,Statement:[...e.Statement,s(t)]}}export{w as ensureEnforceSsl,A as restoreBucketPolicy,B as synthesiseEnforceSslDocument};
1
+ var a=Object.defineProperty;var r=(e,t)=>a(e,"name",{value:t,configurable:!0});import{PutBucketPolicyCommand as m}from"@aws-sdk/client-s3";import{success as f,failure as s}from"@fjall/generator";import{getErrorMessage as l,maskSensitiveOutput as S}from"@fjall/util";import{composeSdkAbortSignal as p,extractErrorName as d,isAccessDenied as y}from"../../aws/organisations/types.js";import{isEnforceSslStatement as k}from"./bucketPolicyTriage.js";const E="2012-10-17";async function $(e,t){const{bucketName:n,policyDocument:c,abortSignal:u}=t;try{await e.send(new m({Bucket:n,Policy:c}),{abortSignal:p(u)})}catch(o){return y(d(o))?s({message:`PutBucketPolicy was denied on ${n}.`,isAccessDenied:!0}):s({message:`Failed to restore the bucket policy on ${n}: ${S(l(o))}`,isAccessDenied:!1})}return f({outcome:"restored"})}r($,"restoreBucketPolicy");function i(e){const t=`arn:aws:s3:::${e}`;return{Sid:"DenyInsecureTransport",Effect:"Deny",Principal:{AWS:"*"},Action:"s3:*",Resource:[t,`${t}/*`],Condition:{Bool:{"aws:SecureTransport":"false"}}}}r(i,"buildEnforceSslStatement");function x(e){return JSON.stringify({Version:E,Statement:[i(e)]})}r(x,"synthesiseEnforceSslDocument");function C(e,t){return e.Statement.some(k)?e:{...e,Statement:[...e.Statement,i(t)]}}r(C,"ensureEnforceSsl");export{C as ensureEnforceSsl,$ as restoreBucketPolicy,x as synthesiseEnforceSslDocument};
@@ -1 +1 @@
1
- function n(t,e,o){return`Access denied for the root session calling ${t} on ${e.kind} ${e.name} (account ${o}). If the organisation's SCPs predate the root-task unlock carve-out, DenyRootUserActions may still block the five s3/sqs policy-management actions \u2014 re-deploy the organisation to refresh its SCPs, then retry.`}export{n as formatScpSuspectedFailure};
1
+ var s=Object.defineProperty;var o=(t,e)=>s(t,"name",{value:e,configurable:!0});function i(t,e,n){return`Access denied for the root session calling ${t} on ${e.kind} ${e.name} (account ${n}). If the organisation's SCPs predate the root-task unlock carve-out, DenyRootUserActions may still block the five s3/sqs policy-management actions \u2014 re-deploy the organisation to refresh its SCPs, then retry.`}o(i,"formatScpSuspectedFailure");export{i as formatScpSuspectedFailure};
@@ -1 +1 @@
1
- import{ResourcePolicyStatementSchema as u}from"@fjall/generator";import{isEnforceSslStatement as c}from"./bucketPolicyTriage.js";function l(n){const e=[],t=[];for(const i of n){if(c(i))continue;const r=s(i);r===void 0?t.push(i):e.push(r)}return{statements:e,unconvertible:t}}function s(n){if(n.NotPrincipal!==void 0||n.NotAction!==void 0||n.NotResource!==void 0)return;const e=n.Effect;if(e!=="Allow"&&e!=="Deny")return;const t=y(n.Principal);if(t===void 0)return;const i=o(n.Action);if(i===void 0||i.length===0)return;const r=o(n.Resource);if(r===void 0||r.length===0)return;const d={effect:e,principals:t,actions:i,resources:r,...typeof n.Sid=="string"&&{sid:n.Sid},...n.Condition!==void 0&&{conditions:n.Condition}},f=u.safeParse(d);if(f.success)return f.data}function y(n){const e=n==="*"?["*"]:S(n)&&Object.keys(n).length===1&&"AWS"in n?o(n.AWS):void 0;if(!(e===void 0||e.length===0))return e}function o(n){if(typeof n=="string")return[n];if(Array.isArray(n))return n.every(e=>typeof e=="string")?n:void 0}function S(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}export{l as toResourcePolicyStatements};
1
+ var c=Object.defineProperty;var t=(n,e)=>c(n,"name",{value:e,configurable:!0});import{ResourcePolicyStatementSchema as s}from"@fjall/generator";import{isEnforceSslStatement as y}from"./bucketPolicyTriage.js";function P(n){const e=[],o=[];for(const i of n){if(y(i))continue;const r=S(i);r===void 0?o.push(i):e.push(r)}return{statements:e,unconvertible:o}}t(P,"toResourcePolicyStatements");function S(n){if(n.NotPrincipal!==void 0||n.NotAction!==void 0||n.NotResource!==void 0)return;const e=n.Effect;if(e!=="Allow"&&e!=="Deny")return;const o=A(n.Principal);if(o===void 0)return;const i=f(n.Action);if(i===void 0||i.length===0)return;const r=f(n.Resource);if(r===void 0||r.length===0)return;const u={effect:e,principals:o,actions:i,resources:r,...typeof n.Sid=="string"&&{sid:n.Sid},...n.Condition!==void 0&&{conditions:n.Condition}},d=s.safeParse(u);if(d.success)return d.data}t(S,"convertStatement");function A(n){const e=n==="*"?["*"]:g(n)&&Object.keys(n).length===1&&"AWS"in n?f(n.AWS):void 0;if(!(e===void 0||e.length===0))return e}t(A,"convertPrincipal");function f(n){if(typeof n=="string")return[n];if(Array.isArray(n))return n.every(e=>typeof e=="string")?n:void 0}t(f,"convertStringList");function g(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}t(g,"isPlainObject");export{P as toResourcePolicyStatements};
@@ -1 +1 @@
1
- import{S3Client as g,GetBucketPolicyCommand as h,DeleteBucketPolicyCommand as E}from"@aws-sdk/client-s3";import{success as l,failure as r}from"@fjall/generator";import{getErrorMessage as d,maskSensitiveOutput as k}from"@fjall/util";import{composeSdkAbortSignal as y,extractErrorName as f,isAccessDenied as S}from"../../aws/organisations/types.js";import{assumeRootForTask as P,MAX_ROOT_SESSION_SECONDS as B}from"../../aws/sts/assumeRoot.js";import{formatScpSuspectedFailure as b}from"./scpRemediation.js";const C="arn:aws:iam::aws:policy/root-task/S3UnlockBucketPolicy";function N(n){return`The unlock deleted the ENTIRE bucket policy on ${n} \u2014 including any enforceSSL deny statements and legitimate access grants it carried, not just the stranded deny. Review the captured policy and re-put sane statements (TLS-only deny, intended access grants) before relying on the bucket.`}async function R(n,w){const{accountId:c,bucketName:e,region:i,abortSignal:a,onPolicyCaptured:m}=w;if(i==="")return r(new Error("A bucket region is required to unlock a bucket."));const u=await P(n,{targetAccountId:c,taskPolicyArn:C,durationSeconds:B,abortSignal:a});if(!u.success)return r(u.error);const p=new g({region:i,credentials:u.data.credentials});let o;try{o=(await p.send(new h({Bucket:e}),{abortSignal:y(a)})).Policy}catch(t){const s=f(t);return s==="NoSuchBucketPolicy"?l({outcome:"no-policy"}):s==="NoSuchBucket"?r(new Error(`Bucket ${e} was not found in region ${i} \u2014 check the bucket name and region.`)):S(s)?r(new Error(b("GetBucketPolicy",{kind:"bucket",name:e},c))):r(new Error(`Failed to capture the bucket policy on ${e}: ${k(d(t))}`))}if(o===void 0||o==="")return l({outcome:"no-policy"});if(m!==void 0)try{await m(o)}catch(t){return r(new Error(`Captured the bucket policy on ${e} but failed to persist it before deletion; left the live policy intact: ${k(d(t))}`))}try{await p.send(new E({Bucket:e}),{abortSignal:y(a)})}catch(t){return S(f(t))?r(new Error(b("DeleteBucketPolicy",{kind:"bucket",name:e},c))):r(new Error(`Captured the bucket policy on ${e} but failed to delete it: ${k(d(t))}`))}return l({outcome:"unlocked",capturedPolicy:o,warning:N(e)})}export{C as S3_UNLOCK_POLICY_ARN,R as unlockBucket};
1
+ var h=Object.defineProperty;var d=(o,c)=>h(o,"name",{value:c,configurable:!0});import{S3Client as E,GetBucketPolicyCommand as P,DeleteBucketPolicyCommand as B}from"@aws-sdk/client-s3";import{success as k,failure as r}from"@fjall/generator";import{getErrorMessage as m,maskSensitiveOutput as p}from"@fjall/util";import{composeSdkAbortSignal as S,extractErrorName as b,isAccessDenied as w}from"../../aws/organisations/types.js";import{assumeRootForTask as C,MAX_ROOT_SESSION_SECONDS as N}from"../../aws/sts/assumeRoot.js";import{formatScpSuspectedFailure as g}from"./scpRemediation.js";const $="arn:aws:iam::aws:policy/root-task/S3UnlockBucketPolicy";function A(o){return`The unlock deleted the ENTIRE bucket policy on ${o} \u2014 including any enforceSSL deny statements and legitimate access grants it carried, not just the stranded deny. Review the captured policy and re-put sane statements (TLS-only deny, intended access grants) before relying on the bucket.`}d(A,"formatPolicyLossWarning");async function v(o,c){const{accountId:i,bucketName:e,region:a,abortSignal:u,onPolicyCaptured:y}=c;if(a==="")return r(new Error("A bucket region is required to unlock a bucket."));const s=await C(o,{targetAccountId:i,taskPolicyArn:$,durationSeconds:N,abortSignal:u});if(!s.success)return r(s.error);const f=new E({region:a,credentials:s.data.credentials});let n;try{n=(await f.send(new P({Bucket:e}),{abortSignal:S(u)})).Policy}catch(t){const l=b(t);return l==="NoSuchBucketPolicy"?k({outcome:"no-policy"}):l==="NoSuchBucket"?r(new Error(`Bucket ${e} was not found in region ${a} \u2014 check the bucket name and region.`)):w(l)?r(new Error(g("GetBucketPolicy",{kind:"bucket",name:e},i))):r(new Error(`Failed to capture the bucket policy on ${e}: ${p(m(t))}`))}if(n===void 0||n==="")return k({outcome:"no-policy"});if(y!==void 0)try{await y(n)}catch(t){return r(new Error(`Captured the bucket policy on ${e} but failed to persist it before deletion; left the live policy intact: ${p(m(t))}`))}try{await f.send(new B({Bucket:e}),{abortSignal:S(u)})}catch(t){return w(b(t))?r(new Error(g("DeleteBucketPolicy",{kind:"bucket",name:e},i))):r(new Error(`Captured the bucket policy on ${e} but failed to delete it: ${p(m(t))}`))}return k({outcome:"unlocked",capturedPolicy:n,warning:A(e)})}d(v,"unlockBucket");export{$ as S3_UNLOCK_POLICY_ARN,v as unlockBucket};
@@ -1 +1 @@
1
- import{SQSClient as A,GetQueueAttributesCommand as Q,SetQueueAttributesCommand as N}from"@aws-sdk/client-sqs";import{success as d,failure as t}from"@fjall/generator";import{getErrorMessage as S,maskSensitiveOutput as p}from"@fjall/util";import{composeSdkAbortSignal as f,extractErrorName as w,isAccessDenied as g}from"../../aws/organisations/types.js";import{assumeRootForTask as q,MAX_ROOT_SESSION_SECONDS as b}from"../../aws/sts/assumeRoot.js";import{formatScpSuspectedFailure as y}from"./scpRemediation.js";const h="arn:aws:iam::aws:policy/root-task/SQSUnlockQueuePolicy",k=new Set(["QueueDoesNotExist","AWS.SimpleQueueService.NonExistentQueue"]);function O(i){return`The unlock cleared the ENTIRE queue policy on ${i} \u2014 including any legitimate access grants it carried (cross-account SendMessage permissions, S3/SNS/EventBridge event-source allow statements), not just the stranded deny. Review the captured policy and re-put sane statements before relying on the queue.`}async function v(i,E){const{accountId:o,queueName:e,region:n,abortSignal:s}=E;if(n==="")return t(new Error("A queue region is required to unlock a queue."));const a=await q(i,{targetAccountId:o,taskPolicyArn:h,durationSeconds:b,abortSignal:s});if(!a.success)return t(a.error);const c=new A({region:n,credentials:a.data.credentials}),l=`https://sqs.${n}.amazonaws.com/${o}/${e}`;let u;try{u=(await c.send(new Q({QueueUrl:l,AttributeNames:["Policy"]}),{abortSignal:f(s)})).Attributes?.Policy}catch(r){const m=w(r);return k.has(m)?t(new Error(`Queue ${e} was not found in region ${n} \u2014 check the queue name and region.`)):g(m)?t(new Error(y("GetQueueAttributes",{kind:"queue",name:e},o))):t(new Error(`Failed to capture the queue policy on ${e}: ${p(S(r))}`))}if(u===void 0||u==="")return d({outcome:"no-policy"});try{await c.send(new N({QueueUrl:l,Attributes:{Policy:""}}),{abortSignal:f(s)})}catch(r){return g(w(r))?t(new Error(y("SetQueueAttributes",{kind:"queue",name:e},o))):t(new Error(`Captured the queue policy on ${e} but failed to clear it: ${p(S(r))}`))}return d({outcome:"unlocked",capturedPolicy:u,warning:O(e)})}export{v as unlockQueue};
1
+ var Q=Object.defineProperty;var l=(o,s)=>Q(o,"name",{value:s,configurable:!0});import{SQSClient as N,GetQueueAttributesCommand as q,SetQueueAttributesCommand as b}from"@aws-sdk/client-sqs";import{success as p,failure as t}from"@fjall/generator";import{getErrorMessage as f,maskSensitiveOutput as w}from"@fjall/util";import{composeSdkAbortSignal as g,extractErrorName as y,isAccessDenied as E}from"../../aws/organisations/types.js";import{assumeRootForTask as h,MAX_ROOT_SESSION_SECONDS as k}from"../../aws/sts/assumeRoot.js";import{formatScpSuspectedFailure as A}from"./scpRemediation.js";const O="arn:aws:iam::aws:policy/root-task/SQSUnlockQueuePolicy",_=new Set(["QueueDoesNotExist","AWS.SimpleQueueService.NonExistentQueue"]);function $(o){return`The unlock cleared the ENTIRE queue policy on ${o} \u2014 including any legitimate access grants it carried (cross-account SendMessage permissions, S3/SNS/EventBridge event-source allow statements), not just the stranded deny. Review the captured policy and re-put sane statements before relying on the queue.`}l($,"formatPolicyLossWarning");async function x(o,s){const{accountId:n,queueName:e,region:u,abortSignal:a}=s;if(u==="")return t(new Error("A queue region is required to unlock a queue."));const c=await h(o,{targetAccountId:n,taskPolicyArn:O,durationSeconds:k,abortSignal:a});if(!c.success)return t(c.error);const m=new N({region:u,credentials:c.data.credentials}),d=`https://sqs.${u}.amazonaws.com/${n}/${e}`;let i;try{i=(await m.send(new q({QueueUrl:d,AttributeNames:["Policy"]}),{abortSignal:g(a)})).Attributes?.Policy}catch(r){const S=y(r);return _.has(S)?t(new Error(`Queue ${e} was not found in region ${u} \u2014 check the queue name and region.`)):E(S)?t(new Error(A("GetQueueAttributes",{kind:"queue",name:e},n))):t(new Error(`Failed to capture the queue policy on ${e}: ${w(f(r))}`))}if(i===void 0||i==="")return p({outcome:"no-policy"});try{await m.send(new b({QueueUrl:d,Attributes:{Policy:""}}),{abortSignal:g(a)})}catch(r){return E(y(r))?t(new Error(A("SetQueueAttributes",{kind:"queue",name:e},n))):t(new Error(`Captured the queue policy on ${e} but failed to clear it: ${w(f(r))}`))}return p({outcome:"unlocked",capturedPolicy:i,warning:$(e)})}l(x,"unlockQueue");export{x as unlockQueue};
@@ -26,6 +26,13 @@ interface DeployStackOptions {
26
26
  */
27
27
  parameters?: Record<string, string>;
28
28
  abortSignal?: AbortSignal;
29
+ /**
30
+ * Deploy only the named stack (`cdk deploy --exclusively`). Set on
31
+ * parallel-phase deploys, where the orchestrator has already deployed the
32
+ * shared dependency stack and sibling cdk processes must not race the
33
+ * fixed-name changeset on it.
34
+ */
35
+ exclusively?: boolean;
29
36
  }
30
37
  interface ParallelStackCallbacks {
31
38
  onOutput?: (chunk: string, stackId: ApplicationStack) => void;
@@ -1 +1 @@
1
- import{CloudFormationClient as R}from"@aws-sdk/client-cloudformation";import{S3Client as v}from"@aws-sdk/client-s3";import{capturedSweepBuckets as D,preEmptyStackBuckets as N,sweepOrphanedDestroyBuckets as F}from"../../orchestration/stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as _,CDK_NO_STACKS_MATCH as L}from"../../types/constants.js";import{assertLeaseBeforeStack as B}from"../../orchestration/application/leaseGate.js";import{getApplicationStackName as h}from"../../types/operations.js";import{success as f,failure as y}from"@fjall/generator";import{ApplicationError as w}from"../../types/application/ApplicationServiceTypes.js";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as P}from"@fjall/util";import{convertCloudFormationOutputsToRecord as O}from"../supporting/helpers.js";import{destroyAllStacks as E,mapSettledResults as A,resolveWebsiteUrl as $}from"./applicationStackHelpers.js";class J{cdkService;cloudFormationService;aws;constructor(e,o,r){this.cdkService=e,this.cloudFormationService=o,this.aws=r}async runCdkSynth(e){return this.cdkService.runCdkSynth(e)}async deployStack(e,o,r,a){const n=o.target,i=h(n,e),c=await this.cdkService.runCdkDeploy(o,i,r?.onOutput,r?.onResourceProgress,this.aws,void 0,a?.parameters);if(c.success){const s=c.data;l.debug("ApplicationStackService","CDK deploy result",{stackName:i,stackType:e,success:!0,message:s.message,status:s.status,skipped:s.details?.skipped,hasOutput:!!s.details?.output});const u=await this.cloudFormationService.getStackOutputs(i,void 0,a?.abortSignal);l.debug("ApplicationStackService","Stack outputs after deploy",{stackName:i,hasOutputs:u.success&&u.data.length>0,outputCount:u.success?u.data.length:0});const t=u.success&&u.data.length>0?O(u.data):void 0;return f({stackType:e,stackName:i,skipped:s.details?.skipped===!0,outputs:t})}else{const s=P(c.error||`Failed to deploy ${e} infrastructure`);return l.error("ApplicationStackService","Stack deployment failed",{stackType:e,target:o.target,error:s}),y(new w(s,{errorType:"deployment_failed",appName:o.target,operation:"deployStack",stackType:e}))}}async deployStacksInParallel(e,o,r){const a=r?.onOutput,n=r?.onResourceProgress,i=r?.onStackComplete,c=await Promise.allSettled(e.map(async s=>{const u=Date.now(),t=await this.deployStack(s,o,{onOutput:a?p=>a(p,s):void 0,onResourceProgress:n?p=>n(p,s):void 0}),S=Date.now()-u,d={stack:s,success:t.success,error:t.success?void 0:t.error,duration:S,...t.success&&t.data.outputs!==void 0?{outputs:t.data.outputs}:{}};return!t.success&&t.error&&l.debug("deployStacksInParallel",`Stack ${s} failed`,{error:t.error.message}),i?.(s,t.success,S,t.success?void 0:t.error),d}));return f(A(c,e))}async destroyStack(e,o,r,a,n,i){const c=o.target,s=h(c,e);let u=[],t=a===!0;this.aws!==void 0&&!t&&(t=(await this.cdkService.runCdkSynth(o)).success);const S=await B(i,s,n,"destroy");if(S!==void 0)return y(S);if(this.aws!==void 0)if(t){const m=await N(this.aws.getClient(R),this.aws.getClient(v),s,{onLog:r?.onLog,onStackCleanupProgress:r?.onStackCleanupProgress},n);u=D(m)}else l.debug("ApplicationStackService","Skipping pre-empty pass after failed validation synth \u2014 the destroy will surface the error",{stackName:s});else l.debug("ApplicationStackService","No AWS provider available, skipping pre-empty pass",{stackName:s});const d=a===!0||t&&o.assemblyDir===void 0,p=await this.cdkService.runCdkDestroy(o,s,r?.onOutput,r?.onResourceProgress,this.aws,d);if(p.success)return this.aws!==void 0&&u.length>0&&await F(this.aws.getClient(v),u,{onLog:r?.onLog,onStackCleanupProgress:r?.onStackCleanupProgress},n),f({stackType:e,stackName:s,skipped:!1,outputs:void 0});{const m=P(p.error||`Failed to destroy ${e} stack`);return l.error("ApplicationStackService","Stack destroy failed",{stackType:e,target:o.target,error:m}),y(new w(m,{errorType:"destroy_failed",appName:o.target,operation:"destroyStack",stackType:e}))}}async destroyStacksInParallel(e,o,r,a,n){const i=r?.onOutput,c=r?.onResourceProgress,s=r?.onStackComplete,u=await Promise.allSettled(e.map(async t=>{const S=Date.now(),d=await this.destroyStack(t,o,{onOutput:i?k=>i(k,t):void 0,onResourceProgress:c?k=>c(k,t):void 0,onLog:r?.onLog,onStackCleanupProgress:r?.onStackCleanupProgress},a,n),p=Date.now()-S,m=d.success?"":d.error?.message??"",C=m.includes(_)||m.includes(L),g={stack:t,success:d.success||C,error:d.success||C?void 0:d.error,duration:p};return!g.success&&g.error&&l.debug("destroyStacksInParallel",`Stack ${t} failed`,{error:g.error.message}),s?.(t,g.success,p,g.success?void 0:g.error),g}));return f(A(u,e))}async getStackOutputs(e,o,r){const a=h(e,o),n=await this.cloudFormationService.getStackOutputs(a,void 0,r);return n.success?f(O(n.data)):y(new w(`Failed to get outputs for ${a}`,{errorType:"stack_error",appName:e,operation:"getStackOutputs",stackType:o,details:n.error}))}async resolveWebsiteUrl(e){return $(e,this.cloudFormationService)}async destroyAllStacks(e,o,r,a,n){return E(this,e,o,r,a,n)}}export{J as ApplicationStackService};
1
+ var N=Object.defineProperty;var P=(C,e)=>N(C,"name",{value:e,configurable:!0});import{CloudFormationClient as F}from"@aws-sdk/client-cloudformation";import{S3Client as O}from"@aws-sdk/client-s3";import{capturedSweepBuckets as _,preEmptyStackBuckets as L,sweepOrphanedDestroyBuckets as B}from"../../orchestration/stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as E,CDK_NO_STACKS_MATCH as $}from"../../types/constants.js";import{assertLeaseBeforeStack as b}from"../../orchestration/application/leaseGate.js";import{getApplicationStackName as h}from"../../types/operations.js";import{success as f,failure as y}from"@fjall/generator";import{ApplicationError as w}from"../../types/application/ApplicationServiceTypes.js";import{logger as p}from"@fjall/util/logger";import{maskSensitiveOutput as A}from"@fjall/util";import{convertCloudFormationOutputsToRecord as R}from"../supporting/helpers.js";import{destroyAllStacks as I,mapSettledResults as D,resolveWebsiteUrl as K}from"./applicationStackHelpers.js";class Y{static{P(this,"ApplicationStackService")}cdkService;cloudFormationService;aws;constructor(e,o,r){this.cdkService=e,this.cloudFormationService=o,this.aws=r}async runCdkSynth(e){return this.cdkService.runCdkSynth(e)}async deployStack(e,o,r,n){const a=o.target,i=h(a,e),c=await this.cdkService.runCdkDeploy(o,i,r?.onOutput,r?.onResourceProgress,this.aws,void 0,n?.parameters,n?.exclusively);if(c.success){const s=c.data;p.debug("ApplicationStackService","CDK deploy result",{stackName:i,stackType:e,success:!0,message:s.message,status:s.status,skipped:s.details?.skipped,hasOutput:!!s.details?.output});const u=await this.cloudFormationService.getStackOutputs(i,void 0,n?.abortSignal);p.debug("ApplicationStackService","Stack outputs after deploy",{stackName:i,hasOutputs:u.success&&u.data.length>0,outputCount:u.success?u.data.length:0});const t=u.success&&u.data.length>0?R(u.data):void 0;return f({stackType:e,stackName:i,skipped:s.details?.skipped===!0,outputs:t})}else{const s=A(c.error||`Failed to deploy ${e} infrastructure`);return p.error("ApplicationStackService","Stack deployment failed",{stackType:e,target:o.target,error:s}),y(new w(s,{errorType:"deployment_failed",appName:o.target,operation:"deployStack",stackType:e}))}}async deployStacksInParallel(e,o,r){const n=r?.onOutput,a=r?.onResourceProgress,i=r?.onStackComplete,c=await Promise.allSettled(e.map(async s=>{const u=Date.now(),t=await this.deployStack(s,o,{onOutput:n?l=>n(l,s):void 0,onResourceProgress:a?l=>a(l,s):void 0},{exclusively:!0}),S=Date.now()-u,d={stack:s,success:t.success,error:t.success?void 0:t.error,duration:S,...t.success&&t.data.outputs!==void 0?{outputs:t.data.outputs}:{}};return!t.success&&t.error&&p.debug("deployStacksInParallel",`Stack ${s} failed`,{error:t.error.message}),i?.(s,t.success,S,t.success?void 0:t.error),d}));return f(D(c,e))}async destroyStack(e,o,r,n,a,i){const c=o.target,s=h(c,e);let u=[],t=n===!0;this.aws!==void 0&&!t&&(t=(await this.cdkService.runCdkSynth(o)).success);const S=await b(i,s,a,"destroy");if(S!==void 0)return y(S);if(this.aws!==void 0)if(t){const m=await L(this.aws.getClient(F),this.aws.getClient(O),s,{onLog:r?.onLog,onStackCleanupProgress:r?.onStackCleanupProgress},a);u=_(m)}else p.debug("ApplicationStackService","Skipping pre-empty pass after failed validation synth \u2014 the destroy will surface the error",{stackName:s});else p.debug("ApplicationStackService","No AWS provider available, skipping pre-empty pass",{stackName:s});const d=n===!0||t&&o.assemblyDir===void 0,l=await this.cdkService.runCdkDestroy(o,s,r?.onOutput,r?.onResourceProgress,this.aws,d);if(l.success)return this.aws!==void 0&&u.length>0&&await B(this.aws.getClient(O),u,{onLog:r?.onLog,onStackCleanupProgress:r?.onStackCleanupProgress},a),f({stackType:e,stackName:s,skipped:!1,outputs:void 0});{const m=A(l.error||`Failed to destroy ${e} stack`);return p.error("ApplicationStackService","Stack destroy failed",{stackType:e,target:o.target,error:m}),y(new w(m,{errorType:"destroy_failed",appName:o.target,operation:"destroyStack",stackType:e}))}}async destroyStacksInParallel(e,o,r,n,a){const i=r?.onOutput,c=r?.onResourceProgress,s=r?.onStackComplete,u=await Promise.allSettled(e.map(async t=>{const S=Date.now(),d=await this.destroyStack(t,o,{onOutput:i?k=>i(k,t):void 0,onResourceProgress:c?k=>c(k,t):void 0,onLog:r?.onLog,onStackCleanupProgress:r?.onStackCleanupProgress},n,a),l=Date.now()-S,m=d.success?"":d.error?.message??"",v=m.includes(E)||m.includes($),g={stack:t,success:d.success||v,error:d.success||v?void 0:d.error,duration:l};return!g.success&&g.error&&p.debug("destroyStacksInParallel",`Stack ${t} failed`,{error:g.error.message}),s?.(t,g.success,l,g.success?void 0:g.error),g}));return f(D(u,e))}async getStackOutputs(e,o,r){const n=h(e,o),a=await this.cloudFormationService.getStackOutputs(n,void 0,r);return a.success?f(R(a.data)):y(new w(`Failed to get outputs for ${n}`,{errorType:"stack_error",appName:e,operation:"getStackOutputs",stackType:o,details:a.error}))}async resolveWebsiteUrl(e){return K(e,this.cloudFormationService)}async destroyAllStacks(e,o,r,n,a){return I(this,e,o,r,n,a)}}export{Y as ApplicationStackService};
@@ -1,4 +1,4 @@
1
- import{maskSensitiveOutput as k}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as A,CDK_NO_STACKS_MATCH as _,INFRASTRUCTURE_FILENAME as I}from"../../types/constants.js";import{APPLICATION_STACKS as L,getApplicationStackName as g,getApplicationDestroyOrder as V,getParallelDestroyGroups as v}from"../../types/operations.js";import{FrameworkRegistry as B}from"../../orchestration/builders/frameworkRegistry.js";import{success as T,failure as l,isFailure as $}from"@fjall/generator";import{ApplicationError as h}from"../../types/application/ApplicationServiceTypes.js";import{assertLeaseBeforeStack as E,LeaseDeniedError as j}from"../../orchestration/application/leaseGate.js";import{logger as x}from"@fjall/util/logger";function U(t,r,e){if(t.success)return t;const n=$(t)?t.error.message:"";return n.includes(A)||n.includes(_)?T({stackName:g(e,r),stackType:r,outputs:{},skipped:!0}):t}function K(t,r){if(t.success)return null;if($(t)&&t.error instanceof j)return{continue:!1,result:l(t.error)};const e=$(t)?k(t.error.message):"";if(e.includes("TSError")||e.includes("Unable to compile TypeScript")||e.includes("Subprocess exited with error")){const n=e.match(/infrastructure\.ts\(\d+,\d+\): error TS\d+: .+/),s=n?n[0]:`Check ${I} for syntax errors`;return{continue:!1,result:l(new h(`CDK synthesis failed: ${s}`,{errorType:"synth_failed",operation:`destroy-${r}`}))}}return e.includes(A)||e.includes(_)?{continue:!0,result:T({message:"Stack already deleted"})}:{continue:!1,result:l(new h(`Failed to destroy ${r} stack: ${e}`,{errorType:"destroy_failed",operation:`destroy-${r}`}))}}async function X(t,r,e,n,s,p){const o=r.target,O=B.createDefault().resolve({appPath:r.path});let N,R;if(O){const u=O.builder.plan({appPath:r.path},O.detection);N=u.parallelDestroy,R=u.destroyOrder}else N=!1,R=V({pattern:null,resources:n});if(N){const u=v();for(const D of u){const c=D.stacks;if(c.length===1){const a=c[0],S=g(o,a),y=await E(p,S,s,"destroy");if(y!==void 0)return l(y);e?.onStackStart?.(a,S);const m=await t.destroyStack(a,r,{onOutput:e?.onOutput?f=>e.onOutput?.(f,a):void 0,onResourceProgress:e?.onResourceProgress?f=>e.onResourceProgress?.(f,a):void 0,onLog:e?.onLog,onStackCleanupProgress:e?.onStackCleanupProgress},void 0,s,p),i=U(m,a,o);e?.onStackComplete&&await e.onStackComplete(a,i);const d=K(i,a);if(d){if(d.continue)continue;return d.result}}else{const a=await E(p,g(o,c[0]),s,"destroy");if(a!==void 0)return l(a);const S=await t.runCdkSynth(r);if(!S.success)return l(new h(S.error,{errorType:"synth_failed",appName:o,operation:"destroy-parallel-synth"}));const y=await E(p,g(o,c[0]),s,"destroy");if(y!==void 0)return l(y);e?.onParallelPhaseStart?.(c,D.description);for(const i of c)e?.onStackStart?.(i,g(o,i));const m=await t.destroyStacksInParallel(c,r,{onOutput:e?.onOutput,onResourceProgress:e?.onResourceProgress,onLog:e?.onLog,onStackCleanupProgress:e?.onStackCleanupProgress,onStackComplete:async(i,d,f,C)=>{const P=C?.message!==void 0?k(C.message):"Stack destruction failed",M=d?T({stackName:g(o,i),stackType:i,outputs:{}}):l(C instanceof h?C:new h(P,{errorType:"destroy_failed",appName:o,operation:`destroy-${i}`}));await e?.onStackComplete?.(i,M)}},!0,s);if(m.success){e?.onParallelPhaseComplete?.(m.data);const i=m.data.filter(d=>!d.success);if(i.length>0){const d=i.filter(f=>f.error&&!f.error.message.includes(A)&&!f.error.message.includes(_));if(d.length>0){const f=d.map(P=>P.stack).join(", "),C=d.map(P=>`${P.stack}: ${P.error?.message??"Unknown error"}`).join(`
2
- `);return l(new h(`Failed to destroy stacks: ${f}
1
+ var V=Object.defineProperty;var C=(t,r)=>V(t,"name",{value:r,configurable:!0});import{maskSensitiveOutput as A}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as _,CDK_NO_STACKS_MATCH as L,INFRASTRUCTURE_FILENAME as v}from"../../types/constants.js";import{APPLICATION_STACKS as $,getApplicationStackName as g,getApplicationDestroyOrder as B,getParallelDestroyGroups as j}from"../../types/operations.js";import{FrameworkRegistry as x}from"../../orchestration/builders/frameworkRegistry.js";import{success as E,failure as l,isFailure as U}from"@fjall/generator";import{ApplicationError as w}from"../../types/application/ApplicationServiceTypes.js";import{assertLeaseBeforeStack as N,LeaseDeniedError as F}from"../../orchestration/application/leaseGate.js";import{logger as G}from"@fjall/util/logger";function K(t,r,e){if(t.success)return t;const n=U(t)?t.error.message:"";return n.includes(_)||n.includes(L)?E({stackName:g(e,r),stackType:r,outputs:{},skipped:!0}):t}C(K,"convertDestroyResultIfNotExists");function M(t,r){if(t.success)return null;if(U(t)&&t.error instanceof F)return{continue:!1,result:l(t.error)};const e=U(t)?A(t.error.message):"";if(e.includes("TSError")||e.includes("Unable to compile TypeScript")||e.includes("Subprocess exited with error")){const n=e.match(/infrastructure\.ts\(\d+,\d+\): error TS\d+: .+/),s=n?n[0]:`Check ${v} for syntax errors`;return{continue:!1,result:l(new w(`CDK synthesis failed: ${s}`,{errorType:"synth_failed",operation:`destroy-${r}`}))}}return e.includes(_)||e.includes(L)?{continue:!0,result:E({message:"Stack already deleted"})}:{continue:!1,result:l(new w(`Failed to destroy ${r} stack: ${e}`,{errorType:"destroy_failed",operation:`destroy-${r}`}))}}C(M,"handleDestroyError");async function b(t,r,e,n,s,p){const o=r.target,O=x.createDefault().resolve({appPath:r.path});let R,k;if(O){const u=O.builder.plan({appPath:r.path},O.detection);R=u.parallelDestroy,k=u.destroyOrder}else R=!1,k=B({pattern:null,resources:n});if(R){const u=j(n);for(const T of u){const c=T.stacks;if(c.length===1){const a=c[0],S=g(o,a),y=await N(p,S,s,"destroy");if(y!==void 0)return l(y);e?.onStackStart?.(a,S);const m=await t.destroyStack(a,r,{onOutput:e?.onOutput?f=>e.onOutput?.(f,a):void 0,onResourceProgress:e?.onResourceProgress?f=>e.onResourceProgress?.(f,a):void 0,onLog:e?.onLog,onStackCleanupProgress:e?.onStackCleanupProgress},void 0,s,p),i=K(m,a,o);e?.onStackComplete&&await e.onStackComplete(a,i);const d=M(i,a);if(d){if(d.continue)continue;return d.result}}else{const a=await N(p,g(o,c[0]),s,"destroy");if(a!==void 0)return l(a);const S=await t.runCdkSynth(r);if(!S.success)return l(new w(S.error,{errorType:"synth_failed",appName:o,operation:"destroy-parallel-synth"}));const y=await N(p,g(o,c[0]),s,"destroy");if(y!==void 0)return l(y);e?.onParallelPhaseStart?.(c,T.description);for(const i of c)e?.onStackStart?.(i,g(o,i));const m=await t.destroyStacksInParallel(c,r,{onOutput:e?.onOutput,onResourceProgress:e?.onResourceProgress,onLog:e?.onLog,onStackCleanupProgress:e?.onStackCleanupProgress,onStackComplete:C(async(i,d,f,P)=>{const h=P?.message!==void 0?A(P.message):"Stack destruction failed",I=d?E({stackName:g(o,i),stackType:i,outputs:{}}):l(P instanceof w?P:new w(h,{errorType:"destroy_failed",appName:o,operation:`destroy-${i}`}));await e?.onStackComplete?.(i,I)},"onStackComplete")},!0,s);if(m.success){e?.onParallelPhaseComplete?.(m.data);const i=m.data.filter(d=>!d.success);if(i.length>0){const d=i.filter(f=>f.error&&!f.error.message.includes(_)&&!f.error.message.includes(L));if(d.length>0){const f=d.map(h=>h.stack).join(", "),P=d.map(h=>`${h.stack}: ${h.error?.message??"Unknown error"}`).join(`
2
+ `);return l(new w(`Failed to destroy stacks: ${f}
3
3
 
4
- ${C}`,{errorType:"destroy_failed",appName:o,operation:"destroy-parallel"}))}}}}}}else for(const u of R){const D=g(o,u),c=await E(p,D,s,"destroy");if(c!==void 0)return l(c);e?.onStackStart?.(u,D);const a=await t.destroyStack(u,r,{onOutput:e?.onOutput?m=>e.onOutput?.(m,u):void 0,onResourceProgress:e?.onResourceProgress?m=>e.onResourceProgress?.(m,u):void 0,onLog:e?.onLog,onStackCleanupProgress:e?.onStackCleanupProgress},void 0,s,p),S=U(a,u,o);e?.onStackComplete&&await e.onStackComplete(u,S);const y=K(S,u);if(y){if(y.continue)continue;return y.result}}return T({message:"All stacks destroyed"})}function Y(t,r){return t.map((e,n)=>e.status==="fulfilled"?e.value:{stack:r[n]??L.NETWORK,success:!1,error:e.reason instanceof Error?e.reason:new Error(String(e.reason)),duration:0})}async function Z(t,r){try{const e=g(t,L.COMPUTE),n=await r.getStackOutputs(e);if(n.success&&n.data.length>0){const o=n.data.find(O=>O.OutputKey?.includes("LoadBalancerUrl"));if(o?.OutputValue)return o.OutputValue.toLowerCase();const w=n.data.find(O=>O.OutputKey?.includes("LoadBalancerDnsName"));if(w?.OutputValue)return`http://${w.OutputValue.toLowerCase()}`}const s=g(t,L.CDN),p=await r.getStackOutputs(s);if(p.success&&p.data.length>0){const o=p.data.find(w=>w.OutputKey?.includes("DistributionDomainName"));if(o?.OutputValue)return`https://${o.OutputValue.toLowerCase()}`}return}catch(e){x.warn("ApplicationStackService","URL resolution failed",{error:k(e instanceof Error?e.message:String(e))});return}}export{U as convertDestroyResultIfNotExists,X as destroyAllStacks,K as handleDestroyError,Y as mapSettledResults,Z as resolveWebsiteUrl};
4
+ ${P}`,{errorType:"destroy_failed",appName:o,operation:"destroy-parallel"}))}}}}}}else for(const u of k){const T=g(o,u),c=await N(p,T,s,"destroy");if(c!==void 0)return l(c);e?.onStackStart?.(u,T);const a=await t.destroyStack(u,r,{onOutput:e?.onOutput?m=>e.onOutput?.(m,u):void 0,onResourceProgress:e?.onResourceProgress?m=>e.onResourceProgress?.(m,u):void 0,onLog:e?.onLog,onStackCleanupProgress:e?.onStackCleanupProgress},void 0,s,p),S=K(a,u,o);e?.onStackComplete&&await e.onStackComplete(u,S);const y=M(S,u);if(y){if(y.continue)continue;return y.result}}return E({message:"All stacks destroyed"})}C(b,"destroyAllStacks");function ee(t,r){return t.map((e,n)=>e.status==="fulfilled"?e.value:{stack:r[n]??$.NETWORK,success:!1,error:e.reason instanceof Error?e.reason:new Error(String(e.reason)),duration:0})}C(ee,"mapSettledResults");async function te(t,r){try{const e=g(t,$.COMPUTE),n=await r.getStackOutputs(e);if(n.success&&n.data.length>0){const o=n.data.find(O=>O.OutputKey?.includes("LoadBalancerUrl"));if(o?.OutputValue)return o.OutputValue.toLowerCase();const D=n.data.find(O=>O.OutputKey?.includes("LoadBalancerDnsName"));if(D?.OutputValue)return`http://${D.OutputValue.toLowerCase()}`}const s=g(t,$.CDN),p=await r.getStackOutputs(s);if(p.success&&p.data.length>0){const o=p.data.find(D=>D.OutputKey?.includes("DistributionDomainName"));if(o?.OutputValue)return`https://${o.OutputValue.toLowerCase()}`}return}catch(e){G.warn("ApplicationStackService","URL resolution failed",{error:A(e instanceof Error?e.message:String(e))});return}}C(te,"resolveWebsiteUrl");export{K as convertDestroyResultIfNotExists,b as destroyAllStacks,M as handleDestroyError,ee as mapSettledResults,te as resolveWebsiteUrl};
@@ -1 +1 @@
1
- import{filterDangerousEnvVars as o}from"@fjall/util";class f{buildContextArgs(r){const e=[];return r?.accountId&&e.push("-c",`accountId=${r.accountId}`),r?.environment&&e.push("-c",`environment=${r.environment}`),r?.managedAccount&&e.push("-c","managedAccount=true"),r?.accountName&&e.push("-c",`accountName=${r.accountName}`),r?.orgId&&e.push("-c",`orgId=${r.orgId}`),r?.rootId&&e.push("-c",`rootId=${r.rootId}`),r?.managementAccountId&&e.push("-c",`managementAccountId=${r.managementAccountId}`),r?.devOuId&&e.push("-c",`fjallDevOuId=${r.devOuId}`),r?.ipamPoolId&&e.push("-c",`ipamPoolId=${r.ipamPoolId}`),r?.fjallOrgId&&e.push("-c",`fjallOrgId=${r.fjallOrgId}`),r?.fjallOidcConfigured&&e.push("-c",`fjallOidcConfigured=${r.fjallOidcConfigured}`),r?.fjallAccountGlobalsConfigured&&e.push("-c",`fjallAccountGlobalsConfigured=${r.fjallAccountGlobalsConfigured}`),r?.fjallAccountTrailState&&e.push("-c",`fjallAccountTrailState=${r.fjallAccountTrailState}`),r?.orgConfig&&e.push("-c",`orgConfig=${r.orgConfig}`),r?.fjallAdoptBackupVault&&e.push("-c","fjallAdoptBackupVault=true"),r?.resolvedSecretArns&&e.push("-c",`fjallResolvedSecretArns=${r.resolvedSecretArns}`),e}buildParameterArgs(r,e){if(r===void 0)return[];const l=Object.entries(r);if(l.length===0)return[];const i=e!==void 0&&e!==""?`${e}:`:"",n=[];for(const[a,u]of l){if(!/^[A-Za-z][A-Za-z0-9]*$/.test(a))throw new Error(`Invalid CloudFormation parameter name "${a}": must match /^[A-Za-z][A-Za-z0-9]*$/ (alphanumeric, leading letter, no separators).`);if(u==="")throw new Error(`CloudFormation parameter "${a}" has an empty value.`);if(/[,\n\r]/.test(u))throw new Error(`CloudFormation parameter "${a}" value contains "," or newline \u2014 cdk's --parameters splits on "," so the deploy would silently fragment.`);n.push("--parameters",`${i}${a}=${u}`)}return n}injectCascadeCredentials(r,e){e&&(r.AWS_ACCESS_KEY_ID=e.accessKeyId,r.AWS_SECRET_ACCESS_KEY=e.secretAccessKey,delete r.AWS_SESSION_TOKEN,e.sessionToken&&(r.AWS_SESSION_TOKEN=e.sessionToken))}buildCdkEnv(r){const e={...o(process.env),CI:"true",FORCE_COLOR:"0",CDK_DISABLE_VERSION_CHECK:"1",NODE_NO_WARNINGS:"1"};return r?.context?.region&&(e.AWS_REGION=r.context.region,e.AWS_DEFAULT_REGION=r.context.region,e.CDK_DEFAULT_REGION=r.context.region),r?.context?.accountId&&(e.CDK_DEFAULT_ACCOUNT=r.context.accountId),this.injectCascadeCredentials(e,r?.credentials),e}}export{f as CdkArgumentBuilder};
1
+ var d=Object.defineProperty;var s=(o,a)=>d(o,"name",{value:a,configurable:!0});import{filterDangerousEnvVars as f}from"@fjall/util";class m{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[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}`),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={...f(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{m as CdkArgumentBuilder};
@@ -1,2 +1,2 @@
1
- import{tmpdir as l}from"os";import{join as C}from"path";import{logger as m}from"@fjall/util/logger";import{success as p,failure as i}from"@fjall/generator";import{CdkError as f}from"../../types/errors/CdkError.js";import{DEFAULT_REGION as O}from"../../aws/utils/regions.js";import{CdkArgumentBuilder as E}from"./CdkArgumentBuilder.js";import{isCdkError as A,formatInfrastructureError as c}from"./CdkErrorFormatter.js";import{hasCdkDifferences as P,parseDiffOutput as _}from"./CdkOutputParser.js";import{DEFAULT_DEPLOY_TIMEOUT_MS as h}from"./CdkEventMonitoring.js";import{analyseBootstrapError as g}from"./CdkOutputAnalyser.js";const R=3e5,y=18e4,L=12e5,k="cdk.out",o=Object.freeze({APP:"--app",CI:"--ci",REQUIRE_APPROVAL:"--require-approval",APPROVAL_NEVER:"never",VERBOSE:"--verbose",NO_LOOKUPS:"--no-lookups",ALL:"--all"});class K{processManager;argumentBuilder=new E;constructor(u){this.processManager=u}dispose(){this.processManager.dispose()}async checkDifferences(u,s,r){const e=["diff"];s?e.push(s):e.push(o.ALL),e.push("--no-color"),r?.noLookups&&e.push(o.NO_LOOKUPS);const t=await this.processManager.runCdkCommand(u,e,{...r,ignoreExitCode:!0,combineOutput:!0});if(!t.success)return i(new f(c(t.error,u),"diff_failed",void 0,void 0,t.error,void 0,!1));const n=t.data.output||"";if(A(n))return i(new f(c(n,u),"diff_failed",void 0,void 0,n,void 0,!1));const a=P(n),d=_(n);return p({hasDifferences:a,output:n,details:d})}async deploy(u,s,r){const e=["deploy"];r?.appDir?e.push(o.APP,r.appDir):r?.useCdkOut&&e.push(o.APP,"cdk.out"),s?e.push(s):e.push(o.ALL),e.push(o.REQUIRE_APPROVAL,o.APPROVAL_NEVER),e.push(o.CI),e.push("--no-version-reporting"),e.push("--no-path-metadata"),e.push("--no-asset-metadata"),r?.verbose?e.push(o.VERBOSE):e.push("--progress","events"),e.push(...this.argumentBuilder.buildParameterArgs(r?.parameters,s));const t={...r,timeout:r?.timeout||h};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(u,e,t):this.processManager.runCdkCommand(u,e,t)}async destroy(u,s,r){const e=["destroy"];r?.appDir?e.push(o.APP,r.appDir):r?.useCdkOut&&e.push(o.APP,"cdk.out"),s?e.push(s):e.push(o.ALL),e.push("--force"),r?.verbose&&e.push(o.VERBOSE);const t={...r,timeout:r?.timeout||h};return this.processManager.runCdkCommand(u,e,t)}async runImport(u,s,r){const e=["import"];r?.stacks&&r.stacks.length>0&&e.push(...r.stacks),s&&e.push("--resource-mapping",s),e.push(o.REQUIRE_APPROVAL,o.APPROVAL_NEVER),e.push(o.CI),r?.noLookups&&e.push(o.NO_LOOKUPS),r?.verbose&&(e.push(o.VERBOSE),e.push("--trace"),e.push("--debug"));const t={...r,timeout:r?.timeout||L};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(u,e,t):this.processManager.runCdkCommand(u,e,t)}async synth(u,s){s?.outputCallback?.(`Synthesising CloudFormation template...
2
- `);const r=s?.noLookups?[o.NO_LOOKUPS]:[],t=["--output",s?.outputDir??C(u,k)],n=s?.context?.region||O,a=await this.processManager.runCdkCommand(u,["synth",...r,...t,o.CI,"--quiet"],{...s,context:{...s?.context,region:n},timeout:s?.timeout||R});if(!a.success){const d=a.error?c(a.error,u):"Failed to synthesise CloudFormation template";return i(d)}return a}async bootstrap(u,s,r){const e=l();m.debug("CdkService","Starting CDK bootstrap",{accountId:u,region:s,bootstrapPath:e,target:`aws://${u}/${s}`});const t=await this.processManager.runCdkCommand(e,["bootstrap",`aws://${u}/${s}`,"--cloudformation-execution-policies","arn:aws:iam::aws:policy/AdministratorAccess",...r?.permissionsBoundary!==void 0&&r.permissionsBoundary!==""?["--custom-permissions-boundary",r.permissionsBoundary]:[],"-c",`accountId=${u}`,o.REQUIRE_APPROVAL,o.APPROVAL_NEVER,o.CI,"--quiet","--force"],{...r,timeout:r?.timeout||y,context:{region:s,accountId:u},credentials:r?.credentials,skipProjectCheck:!0,extraEnv:{TERM:"dumb",CDK_DISABLE_NOTICES:"true",CDK_DISABLE_PROGRESS_BAR:"true"}});return m.debug("CdkService",t.success?"Bootstrap completed successfully":"Bootstrap exited with non-zero code",{accountId:u,region:s,exitCode:t.success?t.data.exitCode:void 0,output:t.success&&t.data.output?.trim()||"(empty)",error:t.success?"(empty)":t.error.trim()||"(empty)"}),t.success?p({output:"AWS environment bootstrapped",exitCode:0}):t.error.includes("already bootstrapped")?p({output:"Environment is already bootstrapped",exitCode:0}):i(g(t.error))}}export{y as BOOTSTRAP_TIMEOUT_MS,K as CdkCommandRunner,L as IMPORT_TIMEOUT_MS,R as SYNTH_TIMEOUT_MS};
1
+ var E=Object.defineProperty;var f=(m,s)=>E(m,"name",{value:s,configurable:!0});import{tmpdir as O}from"os";import{resolve as A}from"path";import{logger as h}from"@fjall/util/logger";import{success as p,failure as i}from"@fjall/generator";import{CdkError as l}from"../../types/errors/CdkError.js";import{DEFAULT_REGION as P}from"../../aws/utils/regions.js";import{CdkArgumentBuilder as _}from"./CdkArgumentBuilder.js";import{isCdkError as g,formatInfrastructureError as c}from"./CdkErrorFormatter.js";import{hasCdkDifferences as L,parseDiffOutput as R}from"./CdkOutputParser.js";import{DEFAULT_DEPLOY_TIMEOUT_MS as C}from"./CdkEventMonitoring.js";import{analyseBootstrapError as y}from"./CdkOutputAnalyser.js";const k=3e5,S=18e4,D=12e5,b="cdk.out",o=Object.freeze({APP:"--app",CI:"--ci",REQUIRE_APPROVAL:"--require-approval",APPROVAL_NEVER:"never",VERBOSE:"--verbose",NO_LOOKUPS:"--no-lookups",ALL:"--all",EXCLUSIVELY:"--exclusively"});class ${static{f(this,"CdkCommandRunner")}processManager;argumentBuilder=new _;constructor(s){this.processManager=s}dispose(){this.processManager.dispose()}async checkDifferences(s,t,r){const e=["diff"];t?e.push(t):e.push(o.ALL),e.push("--no-color"),r?.noLookups&&e.push(o.NO_LOOKUPS);const u=await this.processManager.runCdkCommand(s,e,{...r,ignoreExitCode:!0,combineOutput:!0});if(!u.success)return i(new l(c(u.error,s),"diff_failed",void 0,void 0,u.error,void 0,!1));const a=u.data.output||"";if(g(a))return i(new l(c(a,s),"diff_failed",void 0,void 0,a,void 0,!1));const n=L(a),d=R(a);return p({hasDifferences:n,output:a,details:d})}async deploy(s,t,r){const e=["deploy"];r?.appDir?e.push(o.APP,r.appDir):r?.useCdkOut&&e.push(o.APP,"cdk.out"),t?(e.push(t),r?.exclusively&&e.push(o.EXCLUSIVELY)):e.push(o.ALL),e.push(o.REQUIRE_APPROVAL,o.APPROVAL_NEVER),e.push(o.CI),e.push("--no-version-reporting"),e.push("--no-path-metadata"),e.push("--no-asset-metadata"),r?.verbose?e.push(o.VERBOSE):e.push("--progress","events"),e.push(...this.argumentBuilder.buildParameterArgs(r?.parameters,t));const u={...r,timeout:r?.timeout||C};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(s,e,u):this.processManager.runCdkCommand(s,e,u)}async destroy(s,t,r){const e=["destroy"];r?.appDir?e.push(o.APP,r.appDir):r?.useCdkOut&&e.push(o.APP,"cdk.out"),t?e.push(t):e.push(o.ALL),e.push("--force"),r?.verbose&&e.push(o.VERBOSE);const u={...r,timeout:r?.timeout||C};return this.processManager.runCdkCommand(s,e,u)}async runImport(s,t,r){const e=["import"];r?.stacks&&r.stacks.length>0&&e.push(...r.stacks),t&&e.push("--resource-mapping",t),e.push(o.REQUIRE_APPROVAL,o.APPROVAL_NEVER),e.push(o.CI),r?.noLookups&&e.push(o.NO_LOOKUPS),r?.verbose&&(e.push(o.VERBOSE),e.push("--trace"),e.push("--debug"));const u={...r,timeout:r?.timeout||D};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(s,e,u):this.processManager.runCdkCommand(s,e,u)}async synth(s,t){t?.outputCallback?.(`Synthesising CloudFormation template...
2
+ `);const r=t?.noLookups?[o.NO_LOOKUPS]:[],u=["--output",A(s,t?.outputDir??b)],a=t?.context?.region||P,n=await this.processManager.runCdkCommand(s,["synth",...r,...u,o.CI,"--quiet"],{...t,context:{...t?.context,region:a},timeout:t?.timeout||k});if(!n.success){const d=n.error?c(n.error,s):"Failed to synthesise CloudFormation template";return i(d)}return n}async bootstrap(s,t,r){const e=O();h.debug("CdkService","Starting CDK bootstrap",{accountId:s,region:t,bootstrapPath:e,target:`aws://${s}/${t}`});const u=await this.processManager.runCdkCommand(e,["bootstrap",`aws://${s}/${t}`,"--cloudformation-execution-policies","arn:aws:iam::aws:policy/AdministratorAccess",...r?.permissionsBoundary!==void 0&&r.permissionsBoundary!==""?["--custom-permissions-boundary",r.permissionsBoundary]:[],"-c",`accountId=${s}`,o.REQUIRE_APPROVAL,o.APPROVAL_NEVER,o.CI,"--quiet","--force"],{...r,timeout:r?.timeout||S,context:{region:t,accountId:s},credentials:r?.credentials,skipProjectCheck:!0,extraEnv:{TERM:"dumb",CDK_DISABLE_NOTICES:"true",CDK_DISABLE_PROGRESS_BAR:"true"}});return h.debug("CdkService",u.success?"Bootstrap completed successfully":"Bootstrap exited with non-zero code",{accountId:s,region:t,exitCode:u.success?u.data.exitCode:void 0,output:u.success&&u.data.output?.trim()||"(empty)",error:u.success?"(empty)":u.error.trim()||"(empty)"}),u.success?p({output:"AWS environment bootstrapped",exitCode:0}):u.error.includes("already bootstrapped")?p({output:"Environment is already bootstrapped",exitCode:0}):i(y(u.error))}}export{S as BOOTSTRAP_TIMEOUT_MS,$ as CdkCommandRunner,D as IMPORT_TIMEOUT_MS,k as SYNTH_TIMEOUT_MS};
@@ -1,16 +1,16 @@
1
- import{readFileSync as k}from"fs";import{join as T}from"path";const E=[/Unable to locate credentials/i,/ExpiredToken/i,/InvalidUserPool/i,/AccessDenied/i,/UnauthorizedAccess/i,/AssumeRoleUnauthorizedAccess/i,/The security token included in the request is invalid/i,/Could not assume role/i,/This stack uses assets, so the toolkit stack must be deployed/i,/Has the environment been bootstrapped/i,/Please run 'cdk bootstrap'/i,/requires a newer version of the CDK CLI/i,/Stack .* does not exist/i,/Unable to resolve AWS account/i,/ENOENT.*cdk\.json/i,/Cannot find module/i,/SyntaxError:/i,/TypeError:/i,/ReferenceError:/i,/Error: /i,/❌ .*failed/i,/\[ERROR\]/i,/fatal:/i],x={TS1005:"Check for missing or extra brackets, braces, or commas",TS1135:"Check for mismatched brackets or braces \u2014 a common cause is extra `},` or `]` characters",TS1136:"Check for mismatched braces in object literals",TS1109:"Check for extra commas or mismatched delimiters",TS1003:"Check for syntax errors near the indicated position"};function w(n){return n?E.some(a=>a.test(n)):!1}function C(n){return x[n]??null}function b(n,a,r=1){try{const m=k(n,"utf-8").split(`
2
- `),f=Math.max(0,a-1-r),u=Math.min(m.length,a+r),p=String(u).length,s=[];for(let h=f;h<u;h++){const e=String(h+1).padStart(p),t=h===a-1?">":" ";s.push(`${t} ${e} | ${m[h]}`)}return s.join(`
3
- `)}catch{return null}}function M(n,a){const r=n.split(`
4
- `);if(n.includes("error TS")){const e=/([\w./-]+\.ts)\((\d+),(\d+)\): error (TS\d+): (.+)/,t=[];for(const i of r){const o=i.match(e);o&&t.push({file:o[1],line:parseInt(o[2],10),col:parseInt(o[3],10),code:o[4],message:o[5]})}if(t.length>0){const i=new Set,$=t.filter(c=>{const l=`${c.file}:${c.line}`;return i.has(l)?!1:(i.add(l),!0)}).slice(0,3),g=$[0],d=[];if(d.push(`TypeScript compilation failed in ${g.file}:
5
- `),a){const c=T(a,g.file),l=b(c,g.line);l&&(d.push(l),d.push(""))}for(const c of $){d.push(`Error on line ${c.line}: ${c.message} (${c.code})`);const l=C(c.code);l&&d.push(`Hint: ${l}`)}return d.join(`
6
- `)}}const S=n.match(/Validation failed with the following errors:([\s\S]*?)(?=\n\s*at\s|\n\n|$)/);if(S)return`Validation failed with the following errors:
7
- ${S[1].split(`
1
+ var T=Object.defineProperty;var S=(n,s)=>T(n,"name",{value:s,configurable:!0});import{readFileSync as E}from"fs";import{join as x}from"path";const C=[/Unable to locate credentials/i,/ExpiredToken/i,/InvalidUserPool/i,/AccessDenied/i,/UnauthorizedAccess/i,/AssumeRoleUnauthorizedAccess/i,/The security token included in the request is invalid/i,/Could not assume role/i,/This stack uses assets, so the toolkit stack must be deployed/i,/Has the environment been bootstrapped/i,/Please run 'cdk bootstrap'/i,/requires a newer version of the CDK CLI/i,/Stack .* does not exist/i,/Unable to resolve AWS account/i,/ENOENT.*cdk\.json/i,/Cannot find module/i,/SyntaxError:/i,/TypeError:/i,/ReferenceError:/i,/Error: /i,/❌ .*failed/i,/\[ERROR\]/i,/fatal:/i],b={TS1005:"Check for missing or extra brackets, braces, or commas",TS1135:"Check for mismatched brackets or braces \u2014 a common cause is extra `},` or `]` characters",TS1136:"Check for mismatched braces in object literals",TS1109:"Check for extra commas or mismatched delimiters",TS1003:"Check for syntax errors near the indicated position"};function v(n){return n?C.some(s=>s.test(n)):!1}S(v,"isCdkError");function R(n){return b[n]??null}S(R,"getStructuralHint");function y(n,s,r=1){try{const m=E(n,"utf-8").split(`
2
+ `),f=Math.max(0,s-1-r),u=Math.min(m.length,s+r),p=String(u).length,c=[];for(let h=f;h<u;h++){const e=String(h+1).padStart(p),t=h===s-1?">":" ";c.push(`${t} ${e} | ${m[h]}`)}return c.join(`
3
+ `)}catch{return null}}S(y,"getSourceContext");function A(n,s){const r=n.split(`
4
+ `);if(n.includes("error TS")){const e=/([\w./-]+\.ts)\((\d+),(\d+)\): error (TS\d+): (.+)/,t=[];for(const i of r){const o=i.match(e);o&&t.push({file:o[1],line:parseInt(o[2],10),col:parseInt(o[3],10),code:o[4],message:o[5]})}if(t.length>0){const i=new Set,$=t.filter(a=>{const l=`${a.file}:${a.line}`;return i.has(l)?!1:(i.add(l),!0)}).slice(0,3),g=$[0],d=[];if(d.push(`TypeScript compilation failed in ${g.file}:
5
+ `),s){const a=x(s,g.file),l=y(a,g.line);l&&(d.push(l),d.push(""))}for(const a of $){d.push(`Error on line ${a.line}: ${a.message} (${a.code})`);const l=R(a.code);l&&d.push(`Hint: ${l}`)}return d.join(`
6
+ `)}}const k=n.match(/Validation failed with the following errors:([\s\S]*?)(?=\n\s*at\s|\n\n|$)/);if(k)return`Validation failed with the following errors:
7
+ ${k[1].split(`
8
8
  `).map(t=>t.trim()).filter(t=>t.length>0).map(t=>` ${t}`).join(`
9
9
  `)}`;const m=n.match(/ValidationError:\s*(.+?)(?:\n|$)/);if(m){const e=m[1],t=n.match(/at path \[([^\]]+)\] in ([\w.-]+)/);let i=`${e}`;return t&&(i+=`
10
10
  Construct: ${t[1]}`),e.includes("Cannot find asset")&&e.match(/at (.+)/)?.[1]&&(i+=`
11
- Hint: The build output directory does not exist. Make sure the build step has completed before deploying`),i}let f="",u="",p="",s="";for(let e=0;e<r.length;e++){const t=r[e],i=t.match(/^(\/[^:]+\.ts):(\d+)/);if(i&&!f){const $=i[1],g=i[2];f=`${$.split("/").pop()||$}:${g}`,e+1<r.length&&(u=r[e+1]),e+2<r.length&&r[e+2].includes("^")&&(p=r[e+2])}const o=t.match(/^(\w*Error):\s*(.+)/);o&&!s&&(s=`${o[1]}: ${o[2]}`)}if(f&&s){let e=`Error in ${f}
11
+ Hint: The build output directory does not exist. Make sure the build step has completed before deploying`),i}let f="",u="",p="",c="";for(let e=0;e<r.length;e++){const t=r[e],i=t.match(/^(\/[^:]+\.ts):(\d+)/);if(i&&!f){const $=i[1],g=i[2];f=`${$.split("/").pop()||$}:${g}`,e+1<r.length&&(u=r[e+1]),e+2<r.length&&r[e+2].includes("^")&&(p=r[e+2])}const o=t.match(/^(\w*Error):\s*(.+)/);o&&!c&&(c=`${o[1]}: ${o[2]}`)}if(f&&c){let e=`Error in ${f}
12
12
  `;return u&&(e+=`${u}
13
13
  `),p&&(e+=`${p}
14
14
  `),e+=`
15
- ${s}`,e}return s||r.filter(e=>e.trim()&&e.length<500&&!e.includes("at Module.")&&!e.includes("at Object.")&&!e.includes("at Function.")&&!e.includes("node:internal")&&!e.includes("node_modules/ts-node")&&!e.includes("node_modules/aws-cdk")).slice(0,5).join(`
16
- `)||n}export{M as formatInfrastructureError,b as getSourceContext,C as getStructuralHint,w as isCdkError};
15
+ ${c}`,e}return c||r.filter(e=>e.trim()&&e.length<500&&!e.includes("at Module.")&&!e.includes("at Object.")&&!e.includes("at Function.")&&!e.includes("node:internal")&&!e.includes("node_modules/ts-node")&&!e.includes("node_modules/aws-cdk")).slice(0,5).join(`
16
+ `)||n}S(A,"formatInfrastructureError");export{A as formatInfrastructureError,y as getSourceContext,R as getStructuralHint,v as isCdkError};
@@ -1 +1 @@
1
- import{logger as u}from"@fjall/util/logger";import{getErrorMessage as y,sleep as d}from"@fjall/util";import{CloudFormationFailureAnalyser as g}from"@fjall/util/aws";const F=18e5;function L(t,e,n){return t.waitForStackComplete(e,{timeout:F,pollInterval:2e3,onResourceUpdate:n,onStackComplete:(o,r)=>{}}).catch(o=>(u.debug("CdkService","Stack monitoring failed",{error:y(o)}),{success:!1,failureReason:"Monitoring failed"}))}const m=1e4,p=200;async function S(t,e,n=m){if(t===null)return null;if(e===null)return t.getFailureAnalysis();const o=Date.now()+n;let r;const i=new Promise(a=>{r=setTimeout(()=>a("grace-elapsed"),n)});try{if(await Promise.race([e,i])==="grace-elapsed")return t.getFailureAnalysis()}finally{r!==void 0&&clearTimeout(r)}for(;t.getFailureAnalysis()===null&&Date.now()<o;)await d(p);return t.getFailureAnalysis()}class M{eventLogWriterFactory;onFailureAnalysis;constructor(e){this.eventLogWriterFactory=e?.eventLogWriterFactory,this.onFailureAnalysis=e?.onFailureAnalysis}async createEventMonitor(e,n,o,r,i){const a=await import("../../aws/utils/cloudformationEvents.js"),s=new a.CloudFormationEventMonitor(i,{failureAnalyser:new g,eventLogWriterFactory:this.eventLogWriterFactory,...this.onFailureAnalysis!==void 0?{onFailureAnalysis:this.onFailureAnalysis}:{}}),l=`${e}-${Date.now()}`,c=r.target||r.deployType||"deployment";return s.enableLogging(l,n,o,c),s}}export{M as CdkEventMonitor,F as DEFAULT_DEPLOY_TIMEOUT_MS,m as MONITORING_ANALYSIS_GRACE_MS,S as settleMonitoringAnalysis,L as startStackMonitoring};
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,2 +1,2 @@
1
- import{logger as f}from"@fjall/util/logger";import{maskSensitiveOutput as u}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as m,CDK_NO_STACKS_MATCH as a}from"../../aws/utils/cloudformationEvents.js";import{success as i,failure as l}from"@fjall/generator";import{formatInfrastructureError as k}from"./CdkErrorFormatter.js";import{startStackMonitoring as S}from"./CdkEventMonitoring.js";function x(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"),p=e.includes(a)||!t.success&&t.error.includes(a);if(t.success)return i({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(p)return i({message:"Stack not defined, skipped",status:"SKIPPED",details:{output:e,skipped:!0}});let n=u(t.error||"Deployment failed");if(e.includes("failed:")){const d=e.match(/failed: .*?: (.*?)(?:\n|$)/);d&&(n=u(d[1]))}return f.error("CdkService","CDK deployment returned failure",{errorMessage:n}),l(n)}function A(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
- `)}function P(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":k(e)}function T(e){return e.success?i({message:"Stack destroyed successfully"}):e.error.includes(m)||e.error.includes(a)?i({message:"Stack already deleted or does not exist",details:{skipped:!0}}):l(e.error||"Destroy failed")}function N(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}}function R(e,t,s,o){return c=>{if(e.cdkOutput+=c,t?.(c),!e.stackDetected){const r=N(c);r.detected&&r.stackName&&(e.actualStackName=r.stackName,e.stackDetected=!0),e.stackDetected&&s&&!e.monitoringPromise&&(e.monitoringPromise=S(s,e.actualStackName,o))}}}export{P as analyseBootstrapError,x as analyseDeployOutput,T as analyseDestroyResult,R as createEnhancedOutputCallback,N as detectStackNameFromChunk,A as formatAnalysedDeployFailure};
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,2 +1,2 @@
1
- function c(e){if(!e)return!1;const n=/Number of stacks with differences:\s*(\d+)/,s=e.match(n);if(s&&s[1]){const r=parseInt(s[1],10);if(!isNaN(r))return r>0}return!e.split(`
2
- `).filter(r=>r.trim().startsWith("Stack")).every(r=>r.includes("There were no differences"))}function a(e){const n={hasSecurityChanges:e.includes("(requires replacement)")||e.includes("IAM Statement Changes"),resourceChanges:0,outputChanges:0,resources:[]},s=e.match(/\[\+\]|\[-\]|\[~\]/g);return s&&(n.resourceChanges=s.length),n}export{c as hasCdkDifferences,a as parseDiffOutput};
1
+ var c=Object.defineProperty;var t=(e,s)=>c(e,"name",{value:s,configurable:!0});function f(e){if(!e)return!1;const s=/Number of stacks with differences:\s*(\d+)/,r=e.match(s);if(r&&r[1]){const n=parseInt(r[1],10);if(!isNaN(n))return n>0}return!e.split(`
2
+ `).filter(n=>n.trim().startsWith("Stack")).every(n=>n.includes("There were no differences"))}t(f,"hasCdkDifferences");function o(e){const s={hasSecurityChanges:e.includes("(requires replacement)")||e.includes("IAM Statement Changes"),resourceChanges:0,outputChanges:0,resources:[]},r=e.match(/\[\+\]|\[-\]|\[~\]/g);return r&&(s.resourceChanges=r.length),s}t(o,"parseDiffOutput");export{f as hasCdkDifferences,o as parseDiffOutput};
@@ -1,3 +1,3 @@
1
- import{spawn as M}from"child_process";import{existsSync as b}from"fs";import{join as O}from"path";import{createRequire as A}from"module";import{logger as P}from"@fjall/util/logger";import{filterDangerousEnvVars as j,maskSensitiveOutput as f}from"@fjall/util";import{success as T,failure as n}from"@fjall/generator";import{getErrorMessage as y}from"@fjall/util";function H(){try{const e=A(import.meta.url).resolve("aws-cdk/bin/cdk");return T({command:process.execPath,prefixArgs:[e]})}catch(E){return P.error("CdkService","Failed to resolve the bundled aws-cdk CLI",{error:f(y(E))}),n("Fjall could not locate its bundled aws-cdk CLI. The deploy toolchain looks misconfigured \u2014 reinstall dependencies (npm install) or reinstall the Fjall CLI, then try again.")}}const G=5e3,L=1800*1e3;class W{runningProcesses=new Map;processCounter=0;argBuilder;abortSignal;exitHandler;sigintHandler;sigtermHandler;constructor(e,o){this.argBuilder=e,this.abortSignal=o,this.exitHandler=()=>this.cleanup(),this.sigintHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(130))},this.sigtermHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(143))},process.on("exit",this.exitHandler),process.on("SIGINT",this.sigintHandler),process.on("SIGTERM",this.sigtermHandler)}forceKillProcess(e){e.stdout?.destroy(),e.stderr?.destroy(),e.kill("SIGTERM");const o=setTimeout(()=>{e.exitCode===null&&e.kill("SIGKILL")},G);o.unref(),e.once("exit",()=>clearTimeout(o))}cleanup(){for(const[e,o]of this.runningProcesses)o.killed||(o.stdout?.destroy(),o.stderr?.destroy(),o.kill("SIGTERM"));this.runningProcesses.clear()}dispose(){this.cleanup(),process.removeListener("exit",this.exitHandler),process.removeListener("SIGINT",this.sigintHandler),process.removeListener("SIGTERM",this.sigtermHandler)}async runCdkCommandPassthrough(e,o,t){return new Promise(r=>{if(!b(e)){r(n(`Directory not found: ${e}`));return}const l=O(e,"cdk.json");if(!b(l)){r(n(`No CDK project found in ${e}`));return}const u=H();if(!u.success){r(n(u.error));return}const g=u.data,C=this.argBuilder.buildContextArgs(t?.context),h=[...g.prefixArgs,...o,...C],I=this.argBuilder.buildCdkEnv(t),a=M(g.command,h,{cwd:e,env:I,stdio:"inherit",shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!a.pid){a.on("error",d=>{P.debug("CdkProcess","Spawn error on failed child process",{error:d.message})}),r(n(`Failed to spawn CDK process - no PID. cwd=${e}, args=${h.join(" ")}`));return}const k=`cdk-passthrough-${++this.processCounter}`;this.runningProcesses.set(k,a);let c=!1,m=!1;const p=t?.timeout??L,x=setTimeout(()=>{a.killed||(c=!0,this.forceKillProcess(a))},p);a.on("close",d=>{if(clearTimeout(x),this.runningProcesses.delete(k),!m){if(m=!0,c){r(n("CDK command timed out"));return}d===0||t?.ignoreExitCode?r(T({exitCode:d||0})):r(n(`CDK command failed with exit code ${d}`))}}),a.on("error",d=>{clearTimeout(x),this.runningProcesses.delete(k),!(m||c)&&(m=!0,r(n(`Failed to run CDK command: ${d.message}`)))})})}async runCdkCommand(e,o,t){return new Promise(r=>{if(!b(e)){r(n(`Directory not found: ${e}`));return}if(!t?.skipProjectCheck){const i=O(e,"cdk.json");if(!b(i)){r(n(`No CDK project found in ${e}`));return}}let l="",u="",g=!1;const C=H();if(!C.success){r(n(C.error));return}const h=C.data,I=this.argBuilder.buildContextArgs(t?.context),a=[...h.prefixArgs,...o,...I],k={...this.argBuilder.buildCdkEnv(t),NO_COLOR:"1",...t?.extraEnv?j(t.extraEnv):{}};P.debug("CdkService","Spawning CDK process",{command:`${h.command} ${a.join(" ")}`,workingDir:e});const c=M(h.command,a,{cwd:e,env:k,stdio:["ignore","pipe","pipe"],shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!c.pid){const i=`Failed to spawn CDK process - no PID. cwd=${e}, args=${a.join(" ")}`;c.on("error",s=>{P.debug("CdkProcess","Deferred spawn error on failed child process",{error:s.message})}),r(n(i));return}const m=`cdk-${++this.processCounter}`;this.runningProcesses.set(m,c);let p=!1;const x=t?.timeout??L,d=setTimeout(()=>{c.killed||(g=!0,this.forceKillProcess(c))},x);c.stdout?.on("data",i=>{const s=i.toString();l+=s,t?.outputCallback&&t.outputCallback(f(s)),t?.cdkOutputLogger?.writeCdkOutput("stdout",f(s))}),c.stderr?.on("data",i=>{const s=i.toString();t?.cdkOutputLogger?.writeCdkOutput("stderr",f(s)),!s.includes("deprecated")&&!s.includes("npm WARN")&&!s.includes("ENOENT")&&!/\(node:\d+\)/.test(s)&&!s.includes("ExperimentalWarning")&&!s.includes("will no longer support")&&(u+=s),t?.errorCallback&&t.errorCallback(f(s))}),c.on("error",i=>{clearTimeout(d),this.runningProcesses.delete(m),!(p||g)&&(p=!0,r(n(y(i))))}),c.on("close",i=>{if(clearTimeout(d),this.runningProcesses.delete(m),p)return;if(p=!0,g){r(n("CDK command timed out"));return}const s=i===0||t?.ignoreExitCode===!0&&i===1,R=l+(u?`
2
- ${u}`:"");if(s){const S=t?.combineOutput?R:l;r(T({output:f(S),exitCode:i||0}));return}let $=u;if(l){const S=l.match(/❌.*?Error:(.*)$/m);S&&($=S[1]?.trim()??"")}const K=$||`CDK command failed with exit code ${i}`,w=l?`${K}
3
- ${l}`:K;r(n(f(w)))})})}}export{W as CdkProcessManager};
1
+ var j=Object.defineProperty;var T=(C,e)=>j(C,"name",{value:e,configurable:!0});import{spawn as O}from"child_process";import{existsSync as P}from"fs";import{join as y}from"path";import{createRequire as G}from"module";import{logger as E}from"@fjall/util/logger";import{filterDangerousEnvVars as B,maskSensitiveOutput as f}from"@fjall/util";import{success as $,failure as n}from"@fjall/generator";import{getErrorMessage as H}from"@fjall/util";function L(){try{const e=G(import.meta.url).resolve("aws-cdk/bin/cdk");return $({command:process.execPath,prefixArgs:[e]})}catch(C){return E.error("CdkService","Failed to resolve the bundled aws-cdk CLI",{error:f(H(C))}),n("Fjall could not locate its bundled aws-cdk CLI. The deploy toolchain looks misconfigured \u2014 reinstall dependencies (npm install) or reinstall the Fjall CLI, then try again.")}}T(L,"getCdkBinaryPath");const _=5e3,R=1800*1e3;class z{static{T(this,"CdkProcessManager")}runningProcesses=new Map;processCounter=0;argBuilder;abortSignal;exitHandler;sigintHandler;sigtermHandler;constructor(e,o){this.argBuilder=e,this.abortSignal=o,this.exitHandler=()=>this.cleanup(),this.sigintHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(130))},this.sigtermHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(143))},process.on("exit",this.exitHandler),process.on("SIGINT",this.sigintHandler),process.on("SIGTERM",this.sigtermHandler)}forceKillProcess(e){e.stdout?.destroy(),e.stderr?.destroy(),e.kill("SIGTERM");const o=setTimeout(()=>{e.exitCode===null&&e.kill("SIGKILL")},_);o.unref(),e.once("exit",()=>clearTimeout(o))}cleanup(){for(const[e,o]of this.runningProcesses)o.killed||(o.stdout?.destroy(),o.stderr?.destroy(),o.kill("SIGTERM"));this.runningProcesses.clear()}dispose(){this.cleanup(),process.removeListener("exit",this.exitHandler),process.removeListener("SIGINT",this.sigintHandler),process.removeListener("SIGTERM",this.sigtermHandler)}async runCdkCommandPassthrough(e,o,t){return new Promise(r=>{if(!P(e)){r(n(`Directory not found: ${e}`));return}const l=y(e,"cdk.json");if(!P(l)){r(n(`No CDK project found in ${e}`));return}const u=L();if(!u.success){r(n(u.error));return}const g=u.data,k=this.argBuilder.buildContextArgs(t?.context),h=[...g.prefixArgs,...o,...k],I=this.argBuilder.buildCdkEnv(t),a=O(g.command,h,{cwd:e,env:I,stdio:"inherit",shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!a.pid){a.on("error",d=>{E.debug("CdkProcess","Spawn error on failed child process",{error:d.message})}),r(n(`Failed to spawn CDK process - no PID. cwd=${e}, args=${h.join(" ")}`));return}const x=`cdk-passthrough-${++this.processCounter}`;this.runningProcesses.set(x,a);let c=!1,m=!1;const p=t?.timeout??R,S=setTimeout(()=>{a.killed||(c=!0,this.forceKillProcess(a))},p);a.on("close",d=>{if(clearTimeout(S),this.runningProcesses.delete(x),!m){if(m=!0,c){r(n("CDK command timed out"));return}d===0||t?.ignoreExitCode?r($({exitCode:d||0})):r(n(`CDK command failed with exit code ${d}`))}}),a.on("error",d=>{clearTimeout(S),this.runningProcesses.delete(x),!(m||c)&&(m=!0,r(n(`Failed to run CDK command: ${d.message}`)))})})}async runCdkCommand(e,o,t){return new Promise(r=>{if(!P(e)){r(n(`Directory not found: ${e}`));return}if(!t?.skipProjectCheck){const i=y(e,"cdk.json");if(!P(i)){r(n(`No CDK project found in ${e}`));return}}let l="",u="",g=!1;const k=L();if(!k.success){r(n(k.error));return}const h=k.data,I=this.argBuilder.buildContextArgs(t?.context),a=[...h.prefixArgs,...o,...I],x={...this.argBuilder.buildCdkEnv(t),NO_COLOR:"1",...t?.extraEnv?B(t.extraEnv):{}};E.debug("CdkService","Spawning CDK process",{command:`${h.command} ${a.join(" ")}`,workingDir:e});const c=O(h.command,a,{cwd:e,env:x,stdio:["ignore","pipe","pipe"],shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!c.pid){const i=`Failed to spawn CDK process - no PID. cwd=${e}, args=${a.join(" ")}`;c.on("error",s=>{E.debug("CdkProcess","Deferred spawn error on failed child process",{error:s.message})}),r(n(i));return}const m=`cdk-${++this.processCounter}`;this.runningProcesses.set(m,c);let p=!1;const S=t?.timeout??R,d=setTimeout(()=>{c.killed||(g=!0,this.forceKillProcess(c))},S);c.stdout?.on("data",i=>{const s=i.toString();l+=s,t?.outputCallback&&t.outputCallback(f(s)),t?.cdkOutputLogger?.writeCdkOutput("stdout",f(s))}),c.stderr?.on("data",i=>{const s=i.toString();t?.cdkOutputLogger?.writeCdkOutput("stderr",f(s)),!s.includes("deprecated")&&!s.includes("npm WARN")&&!s.includes("ENOENT")&&!/\(node:\d+\)/.test(s)&&!s.includes("ExperimentalWarning")&&!s.includes("will no longer support")&&(u+=s),t?.errorCallback&&t.errorCallback(f(s))}),c.on("error",i=>{clearTimeout(d),this.runningProcesses.delete(m),!(p||g)&&(p=!0,r(n(H(i))))}),c.on("close",i=>{if(clearTimeout(d),this.runningProcesses.delete(m),p)return;if(p=!0,g){r(n("CDK command timed out"));return}const s=i===0||t?.ignoreExitCode===!0&&i===1,w=l+(u?`
2
+ ${u}`:"");if(s){const b=t?.combineOutput?w:l;r($({output:f(b),exitCode:i||0}));return}let K=u;if(l){const b=l.match(/❌.*?Error:(.*)$/m);b&&(K=b[1]?.trim()??"")}const M=K||`CDK command failed with exit code ${i}`,A=l?`${M}
3
+ ${l}`:M;r(n(f(A)))})})}}export{z as CdkProcessManager};
@@ -22,6 +22,6 @@ export declare class CdkService {
22
22
  runCdkSynth(context: DeploymentContext, onOutput?: (chunk: string) => void, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
23
23
  runCdkBootstrap(context: DeploymentContext, onOutput?: (chunk: string) => void, credentials?: CdkOptions["credentials"], permissionsBoundary?: string): Promise<Result<StepOutput, string>>;
24
24
  runCdkDiff(context: DeploymentContext, onOutput?: (chunk: string) => void): Promise<Result<StepOutput, string>>;
25
- runCdkDeploy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, credentials?: CdkOptions["credentials"], parameters?: Record<string, string>): Promise<Result<StepOutput, string>>;
25
+ runCdkDeploy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, credentials?: CdkOptions["credentials"], parameters?: Record<string, string>, exclusively?: boolean): Promise<Result<StepOutput, string>>;
26
26
  runCdkDestroy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, useCdkOut?: boolean, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
27
27
  }
@@ -1,3 +1,3 @@
1
- import{existsSync as N}from"fs";import{join as A}from"path";import{logger as I}from"@fjall/util/logger";import{success as v,failure as o}from"@fjall/generator";import{DEFAULT_REGION as b}from"../../aws/utils/regions.js";import{getErrorMessage as D,maskSensitiveOutput as g}from"@fjall/util";import{CdkEventMonitor as E,settleMonitoringAnalysis as T,startStackMonitoring as O}from"./CdkEventMonitoring.js";import{analyseDeployOutput as K,analyseDestroyResult as L,createEnhancedOutputCallback as P,formatAnalysedDeployFailure as W}from"./CdkOutputAnalyser.js";import{recordDriftSuspects as $}from"../../orchestration/drift/driftSuspectJournal.js";import{CdkCommandRunner as _}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as j}from"./CdkArgumentBuilder.js";import{CdkProcessManager as G}from"./CdkProcessManager.js";import{cancelInFlightStack as q}from"./cancelInFlightStack.js";import{isAborted as M}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as B}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as U,resolveStackName as F,getFallbackStackName as z,buildDeploymentCdkContext as C}from"./cdkServiceHelpers.js";class le{commandRunner;eventMonitor;abortSignal;constructor(e){const t=e?.processManager??new G(new j,e?.abortSignal);this.commandRunner=new _(t),this.eventMonitor=new E({eventLogWriterFactory:e?.eventLogWriterFactory,...e?.onFailureAnalysis!==void 0?{onFailureAnalysis:e.onFailureAnalysis}:{}}),this.abortSignal=e?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(e,t,r){return this.commandRunner.checkDifferences(e,t,r)}async deploy(e,t,r){return this.commandRunner.deploy(e,t,r)}async destroy(e,t,r){return this.commandRunner.destroy(e,t,r)}async runImport(e,t,r){return this.commandRunner.runImport(e,t,r)}async synth(e,t){return this.commandRunner.synth(e,t)}async bootstrap(e,t,r){return this.commandRunner.bootstrap(e,t,r)}async runCdkSynth(e,t,r){const n=e.callerIdentity?.Account;try{const a=await this.synth(e.path,{outputCallback:t,context:C(e,n,e.region||b),...e.assemblyDir!==void 0?{outputDir:e.assemblyDir}:{},...r!==void 0?{credentials:r}:{}});return a.success?v({message:"CloudFormation template synthesised",details:a.data.output?{synthesisTime:a.data.output}:void 0}):o(a.error||"Failed to synthesise CloudFormation template")}catch(a){return o(`CDK synth failed: ${g(D(a))}`)}}async runCdkBootstrap(e,t,r,n){const a=e.callerIdentity?.Account,p=e.region||b;try{if(!a)return o("No AWS account ID available");const l=A(e.path,"node_modules");if(!N(l))return o(`Dependencies not installed. Please run 'npm install' in ${e.path} before deploying.`);const u=await this.bootstrap(a,p,{outputCallback:t,credentials:r,...n!==void 0&&n!==""?{permissionsBoundary:n}:{}});return u.success?v({message:"AWS environment bootstrapped"}):o(u.error||"Failed to bootstrap AWS environment")}catch(l){return o(`CDK bootstrap failed: ${g(D(l))}`)}}async runCdkDiff(e,t){const r=e.callerIdentity?.Account;try{const n=await this.checkDifferences(e.path,void 0,{verbose:e.options?.verbose,outputCallback:t,context:C(e,r,e.region||b)});return n.success?v({message:"Diff check complete",details:{hasDifferences:n.data.hasDifferences,details:n.data.details}}):o(`CDK diff failed: ${n.error.message}`)}catch(n){return o(`CDK diff failed: ${g(D(n))}`)}}async runCdkDeploy(e,t,r,n,a,p,l){const u=e.callerIdentity?.Account,m=e.region||b;if(!u)return o("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!a)return o("AwsProvider is required for deployment monitoring.");const f=e.assemblyDir??A(e.path,"cdk.out"),d=B(f,n);let s=null,h;try{const c=F(t,e)??z(e);s=await this.eventMonitor.createEventMonitor("deploy",c,m,e,a),r&&(r(g(`Starting CloudFormation deployment of ${c}...
1
+ var O=Object.defineProperty;var M=(I,e)=>O(I,"name",{value:e,configurable:!0});import{existsSync as K}from"fs";import{join as F}from"path";import{logger as R}from"@fjall/util/logger";import{success as A,failure as o}from"@fjall/generator";import{DEFAULT_REGION as k}from"../../aws/utils/regions.js";import{getErrorMessage as b,maskSensitiveOutput as g}from"@fjall/util";import{CdkEventMonitor as L,settleMonitoringAnalysis as P,startStackMonitoring as W}from"./CdkEventMonitoring.js";import{analyseDeployOutput as $,analyseDestroyResult as _,createEnhancedOutputCallback as j,formatAnalysedDeployFailure as G}from"./CdkOutputAnalyser.js";import{recordDriftSuspects as q}from"../../orchestration/drift/driftSuspectJournal.js";import{CdkCommandRunner as B}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as U}from"./CdkArgumentBuilder.js";import{CdkProcessManager as z}from"./CdkProcessManager.js";import{cancelInFlightStack as H}from"./cancelInFlightStack.js";import{isAborted as w}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as J}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as Q,resolveStackName as N,getFallbackStackName as V,buildDeploymentCdkContext as C}from"./cdkServiceHelpers.js";class fe{static{M(this,"CdkService")}commandRunner;eventMonitor;abortSignal;constructor(e){const t=e?.processManager??new z(new U,e?.abortSignal);this.commandRunner=new B(t),this.eventMonitor=new L({eventLogWriterFactory:e?.eventLogWriterFactory,...e?.onFailureAnalysis!==void 0?{onFailureAnalysis:e.onFailureAnalysis}:{}}),this.abortSignal=e?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(e,t,r){return this.commandRunner.checkDifferences(e,t,r)}async deploy(e,t,r){return this.commandRunner.deploy(e,t,r)}async destroy(e,t,r){return this.commandRunner.destroy(e,t,r)}async runImport(e,t,r){return this.commandRunner.runImport(e,t,r)}async synth(e,t){return this.commandRunner.synth(e,t)}async bootstrap(e,t,r){return this.commandRunner.bootstrap(e,t,r)}async runCdkSynth(e,t,r){const n=e.callerIdentity?.Account;try{const a=await this.synth(e.path,{outputCallback:t,context:C(e,n,e.region||k),...e.assemblyDir!==void 0?{outputDir:e.assemblyDir}:{},...r!==void 0?{credentials:r}:{}});return a.success?A({message:"CloudFormation template synthesised",details:a.data.output?{synthesisTime:a.data.output}:void 0}):o(a.error||"Failed to synthesise CloudFormation template")}catch(a){return o(`CDK synth failed: ${g(b(a))}`)}}async runCdkBootstrap(e,t,r,n){const a=e.callerIdentity?.Account,f=e.region||k;try{if(!a)return o("No AWS account ID available");const l=F(e.path,"node_modules");if(!K(l))return o(`Dependencies not installed. Please run 'npm install' in ${e.path} before deploying.`);const d=await this.bootstrap(a,f,{outputCallback:t,credentials:r,...n!==void 0&&n!==""?{permissionsBoundary:n}:{}});return d.success?A({message:"AWS environment bootstrapped"}):o(d.error||"Failed to bootstrap AWS environment")}catch(l){return o(`CDK bootstrap failed: ${g(b(l))}`)}}async runCdkDiff(e,t){const r=e.callerIdentity?.Account;try{const n=await this.checkDifferences(e.path,void 0,{verbose:e.options?.verbose,outputCallback:t,context:C(e,r,e.region||k)});return n.success?A({message:"Diff check complete",details:{hasDifferences:n.data.hasDifferences,details:n.data.details}}):o(`CDK diff failed: ${n.error.message}`)}catch(n){return o(`CDK diff failed: ${g(b(n))}`)}}async runCdkDeploy(e,t,r,n,a,f,l,d){const p=e.callerIdentity?.Account,c=e.region||k;if(!p)return o("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!a)return o("AwsProvider is required for deployment monitoring.");const m=e.assemblyDir??F(e.path,"cdk.out"),D=J(m,n);let s=null,S;try{const u=N(t,e)??V(e);s=await this.eventMonitor.createEventMonitor("deploy",u,c,e,a),r&&(r(g(`Starting CloudFormation deployment of ${u}...
2
2
  `)),r(`Monitoring CloudFormation events (CDK process running in background)...
3
- `));const i={cdkOutput:"",actualStackName:c,stackDetected:!1,monitoringPromise:null},R=P(i,r,s,d);h=setTimeout(()=>{!i.stackDetected&&s&&!i.monitoringPromise&&(I.debug("CdkService","Fallback monitoring STARTING",{targetStackName:c,stackDetected:i.stackDetected,hasOnResourceProgress:!!n}),i.monitoringPromise=O(s,c,d))},U);const w=await this.deploy(e.path,c,{verbose:e.options?.verbose,outputCallback:R,...e.assemblyDir!==void 0?{appDir:e.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:s?.getEventLogger()??void 0,context:C(e,u,m),credentials:p,...l!==void 0&&Object.keys(l).length>0&&{parameters:l}});if(M(this.abortSignal))return await q(c,m,p,r),o("Deployment cancelled");const S=K(i.cdkOutput,w,i.actualStackName);if(S.success)return S;const k=await T(s,i.monitoringPromise);return k===null?S:(k.driftSuspects!==void 0&&k.driftSuspects.length>0&&await $({accountId:u,region:m,stackName:i.actualStackName,recordedAt:new Date().toISOString(),suspects:k.driftSuspects.map(y=>({stackName:i.actualStackName,logicalId:y.logicalId,resourceType:y.resourceType,...y.physicalId!==void 0?{physicalId:y.physicalId}:{},failedStatus:y.failedStatus,statusReason:y.statusReason}))}),o(W(k)))}catch(c){const i=`CDK deploy failed: ${g(D(c))}`;return I.error("CdkService","CDK deployment exception",{error:i}),o(i)}finally{clearTimeout(h),s&&s.stopMonitoring()}}async runCdkDestroy(e,t,r,n,a,p,l){const u=e.callerIdentity?.Account,m=e.region||b;let f=null;try{const d=F(t,e);u&&d&&a&&(f=await this.eventMonitor.createEventMonitor("destroy",d,m,e,a),f.startMonitoring(d,h=>{n?.(h)},(h,c)=>{}));const s=await this.destroy(e.path,t,{verbose:e.options?.verbose,outputCallback:r,useCdkOut:p,cdkOutputLogger:f?.getEventLogger()??void 0,context:C(e,u,m),credentials:l});return M(this.abortSignal)?o("Destroy cancelled"):L(s)}catch(d){return o(`CDK destroy failed: ${g(D(d))}`)}finally{f&&f.stopMonitoring()}}}export{le as CdkService};
3
+ `));const i={cdkOutput:"",actualStackName:u,stackDetected:!1,monitoringPromise:null},E=j(i,r,s,D);S=setTimeout(()=>{!i.stackDetected&&s&&!i.monitoringPromise&&(R.debug("CdkService","Fallback monitoring STARTING",{targetStackName:u,stackDetected:i.stackDetected,hasOnResourceProgress:!!n}),i.monitoringPromise=W(s,u,D))},Q);const T=await this.deploy(e.path,u,{verbose:e.options?.verbose,outputCallback:E,...e.assemblyDir!==void 0?{appDir:e.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:s?.getEventLogger()??void 0,context:C(e,p,c),credentials:f,...l!==void 0&&Object.keys(l).length>0&&{parameters:l},...d===!0&&{exclusively:!0}});if(w(this.abortSignal))return await H(u,c,f,r),o("Deployment cancelled");const v=$(i.cdkOutput,T,i.actualStackName);if(v.success)return v;const h=await P(s,i.monitoringPromise);return h===null?v:(h.driftSuspects!==void 0&&h.driftSuspects.length>0&&await q({accountId:p,region:c,stackName:i.actualStackName,recordedAt:new Date().toISOString(),suspects:h.driftSuspects.map(y=>({stackName:i.actualStackName,logicalId:y.logicalId,resourceType:y.resourceType,...y.physicalId!==void 0?{physicalId:y.physicalId}:{},failedStatus:y.failedStatus,statusReason:y.statusReason}))}),o(G(h)))}catch(u){const i=`CDK deploy failed: ${g(b(u))}`;return R.error("CdkService","CDK deployment exception",{error:i}),o(i)}finally{clearTimeout(S),s&&s.stopMonitoring()}}async runCdkDestroy(e,t,r,n,a,f,l){const d=e.callerIdentity?.Account,p=e.region||k;let c=null;try{const m=N(t,e);d&&m&&a&&(c=await this.eventMonitor.createEventMonitor("destroy",m,p,e,a),c.startMonitoring(m,s=>{n?.(s)},(s,S)=>{}));const D=await this.destroy(e.path,t,{verbose:e.options?.verbose,outputCallback:r,useCdkOut:f,cdkOutputLogger:c?.getEventLogger()??void 0,context:C(e,d,p),credentials:l});return w(this.abortSignal)?o("Destroy cancelled"):_(D)}catch(m){return o(`CDK destroy failed: ${g(b(m))}`)}finally{c&&c.stopMonitoring()}}}export{fe as CdkService};
@@ -32,6 +32,35 @@ export interface CdkContext {
32
32
  * key literal in sync with that reader.
33
33
  */
34
34
  resolvedSecretArns?: string;
35
+ /**
36
+ * Record<zoneName, JSON ManagedDomainBinding> of deploy-time-resolved
37
+ * managed-domain bindings (D2). Emitted as one
38
+ * `-c fjall:managedDomain:<zoneName>=<json>` context arg per entry and
39
+ * consumed by `readInjectedManagedDomainBinding` in
40
+ * @fjall/components-infrastructure (`utils/managedDomainContext.ts`). Keep
41
+ * the key prefix literal in sync with that reader
42
+ * (MANAGED_DOMAIN_CONTEXT_PREFIX).
43
+ */
44
+ managedDomainBindings?: Record<string, string>;
45
+ /**
46
+ * Two-phase delegated-domain deploy gate (D8): `"zone"` deploys the child
47
+ * zone + NS delegation only, `"full"` additionally issues certificates.
48
+ * Emitted as `-c fjall:domainDeployPhase=<phase>` and consumed by the
49
+ * GENERATED domain `infrastructure.ts` (CLI `tsEmitter`), which passes it
50
+ * to the Domain construct as the `phase` prop. Keep the key literal in
51
+ * sync with that emitter.
52
+ */
53
+ domainDeployPhase?: string;
54
+ /**
55
+ * Parent DelegationRole ARN for a delegated child domain deploy (D8
56
+ * child-writes delegation) — always a LITERAL resolved from the parent
57
+ * domain stack's outputs (DescribeStacks), never `Fn.importValue` (VD2-a).
58
+ * Emitted as `-c fjall:parentDelegationRoleArn=<arn>`, mirroring the
59
+ * `fjall:managedDomain:<zone>` binding channel, and consumed by the
60
+ * generated domain `infrastructure.ts` (CLI `tsEmitter`) as the
61
+ * `parentDelegationRoleArn` prop.
62
+ */
63
+ parentDelegationRoleArn?: string;
35
64
  }
36
65
  export interface CdkOptions {
37
66
  verbose?: boolean;
@@ -57,6 +86,16 @@ export interface CdkOptions {
57
86
  * boundary is known to exist. Absent/empty ⇒ today's behaviour (no boundary).
58
87
  */
59
88
  permissionsBoundary?: string;
89
+ /**
90
+ * Pass `--exclusively` to `cdk deploy <stack>`: deploy only the named
91
+ * stack, never the dependency stacks cdk would otherwise pull in. Set by
92
+ * orchestrators that own cross-stack ordering themselves — REQUIRED for
93
+ * any stacks deployed concurrently (parallel Phase 2), where sibling cdk
94
+ * processes otherwise race the fixed-name changeset on their shared
95
+ * dependency stack (CFN ClientToken mismatch). Ignored without a stack
96
+ * name (`--all`).
97
+ */
98
+ exclusively?: boolean;
60
99
  passThroughCDK?: boolean;
61
100
  stacks?: string[];
62
101
  noLookups?: boolean;
@@ -1 +1 @@
1
- import{mkdir as y,readFile as l,rename as g,writeFile as p}from"node:fs/promises";import{homedir as w}from"node:os";import{join as u}from"node:path";import{CloudFormationClient as S,DescribeTypeCommand as b}from"@aws-sdk/client-cloudformation";import{z as a}from"zod";import{success as c,failure as h}from"@fjall/generator";import{getErrorMessage as f,maskSensitiveOutput as n}from"@fjall/util";import{logger as o}from"@fjall/util/logger";import{composeSdkAbortSignal as C}from"../../aws/organisations/types.js";import{CFN_REGISTRY_SEED as D}from"./cfnRegistrySeed.js";const d="CfnRegistryService",O=a.object({createOnlyProperties:a.array(a.string()).optional(),conditionalCreateOnlyProperties:a.array(a.string()).optional(),primaryIdentifier:a.array(a.string()).optional()}),P=a.object({typeName:a.string(),createOnlyProperties:a.array(a.string()),conditionalCreateOnlyProperties:a.array(a.string()),primaryIdentifier:a.array(a.string())}).strict(),T=a.object({cachedAt:a.string(),registryLastUpdated:a.string().optional(),summary:P}).strict(),E=10080*60*1e3;class _{aws;cacheDir;cacheTtlMs;seed;now;memory=new Map;constructor(e,t){this.aws=e,this.cacheDir=t?.cacheDir??u(w(),".fjall","cache","cfn-registry"),this.cacheTtlMs=t?.cacheTtlMs??E,this.seed=t?.seed??D,this.now=t?.now??(()=>Date.now())}async getSchemaSummary(e,t){const s=this.memory.get(e);if(s!==void 0)return c(s);const r=await this.readCache(e);if(r!==void 0&&this.isFresh(r))return this.memory.set(e,r.summary),c(r.summary);const i=await this.describeType(e,t);if(i.success)return this.memory.set(e,i.data.summary),await this.writeCache(e,i.data),c(i.data.summary);if(r!==void 0)return o.warn(d,"Live DescribeType failed \u2014 serving stale cache",{typeName:e,error:n(i.error.message)}),this.memory.set(e,r.summary),c(r.summary);const m=this.seed[e];return m!==void 0?(o.warn(d,"Live DescribeType failed \u2014 serving baked seed",{typeName:e,error:n(i.error.message)}),this.memory.set(e,m),c(m)):h(i.error)}isFresh(e){const t=Date.parse(e.cachedAt);return Number.isNaN(t)?!1:this.now()-t<=this.cacheTtlMs}cachePath(e){const t=`${e.replaceAll("::","-")}.json`;return u(this.cacheDir,this.aws.getRegion(),t)}async describeType(e,t){try{const r=await this.aws.getClient(S).send(new b({Type:"RESOURCE",TypeName:e}),{abortSignal:C(t)});if(r.Schema===void 0||r.Schema==="")return h(new Error(`DescribeType returned no schema for ${e}`));const i=O.safeParse(JSON.parse(r.Schema));return i.success?c({cachedAt:new Date(this.now()).toISOString(),...r.LastUpdated!==void 0?{registryLastUpdated:r.LastUpdated.toISOString()}:{},summary:{typeName:e,createOnlyProperties:i.data.createOnlyProperties??[],conditionalCreateOnlyProperties:i.data.conditionalCreateOnlyProperties??[],primaryIdentifier:i.data.primaryIdentifier??[]}}):h(new Error(`Unparsable registry schema for ${e}: ${n(i.error.message)}`))}catch(s){return h(new Error(`DescribeType failed for ${e}: ${n(f(s))}`))}}async readCache(e){const t=this.cachePath(e);try{const s=await l(t,"utf8"),r=T.safeParse(JSON.parse(s));if(!r.success){o.debug(d,"Discarding unparsable registry cache entry",{path:t,error:n(r.error.message)});return}return r.data}catch(s){o.debug(d,"Registry cache read missed",{path:t,error:n(f(s))});return}}async writeCache(e,t){const s=this.cachePath(e);try{await y(u(this.cacheDir,this.aws.getRegion()),{recursive:!0});const r=`${s}.tmp-${process.pid}`;await p(r,JSON.stringify(t,null,2),"utf8"),await g(r,s)}catch(r){o.debug(d,"Registry cache write failed",{path:s,error:n(f(r))})}}}export{_ as CfnRegistryService};
1
+ var g=Object.defineProperty;var l=(y,e)=>g(y,"name",{value:e,configurable:!0});import{mkdir as p,readFile as w,rename as S,writeFile as b}from"node:fs/promises";import{homedir as C}from"node:os";import{join as u}from"node:path";import{CloudFormationClient as D,DescribeTypeCommand as O}from"@aws-sdk/client-cloudformation";import{z as a}from"zod";import{success as c,failure as h}from"@fjall/generator";import{getErrorMessage as f,maskSensitiveOutput as n}from"@fjall/util";import{logger as o}from"@fjall/util/logger";import{composeSdkAbortSignal as P}from"../../aws/organisations/types.js";import{CFN_REGISTRY_SEED as T}from"./cfnRegistrySeed.js";const d="CfnRegistryService",E=a.object({createOnlyProperties:a.array(a.string()).optional(),conditionalCreateOnlyProperties:a.array(a.string()).optional(),primaryIdentifier:a.array(a.string()).optional()}),R=a.object({typeName:a.string(),createOnlyProperties:a.array(a.string()),conditionalCreateOnlyProperties:a.array(a.string()),primaryIdentifier:a.array(a.string())}).strict(),v=a.object({cachedAt:a.string(),registryLastUpdated:a.string().optional(),summary:R}).strict(),$=10080*60*1e3;class Y{static{l(this,"CfnRegistryService")}aws;cacheDir;cacheTtlMs;seed;now;memory=new Map;constructor(e,t){this.aws=e,this.cacheDir=t?.cacheDir??u(C(),".fjall","cache","cfn-registry"),this.cacheTtlMs=t?.cacheTtlMs??$,this.seed=t?.seed??T,this.now=t?.now??(()=>Date.now())}async getSchemaSummary(e,t){const s=this.memory.get(e);if(s!==void 0)return c(s);const r=await this.readCache(e);if(r!==void 0&&this.isFresh(r))return this.memory.set(e,r.summary),c(r.summary);const i=await this.describeType(e,t);if(i.success)return this.memory.set(e,i.data.summary),await this.writeCache(e,i.data),c(i.data.summary);if(r!==void 0)return o.warn(d,"Live DescribeType failed \u2014 serving stale cache",{typeName:e,error:n(i.error.message)}),this.memory.set(e,r.summary),c(r.summary);const m=this.seed[e];return m!==void 0?(o.warn(d,"Live DescribeType failed \u2014 serving baked seed",{typeName:e,error:n(i.error.message)}),this.memory.set(e,m),c(m)):h(i.error)}isFresh(e){const t=Date.parse(e.cachedAt);return Number.isNaN(t)?!1:this.now()-t<=this.cacheTtlMs}cachePath(e){const t=`${e.replaceAll("::","-")}.json`;return u(this.cacheDir,this.aws.getRegion(),t)}async describeType(e,t){try{const r=await this.aws.getClient(D).send(new O({Type:"RESOURCE",TypeName:e}),{abortSignal:P(t)});if(r.Schema===void 0||r.Schema==="")return h(new Error(`DescribeType returned no schema for ${e}`));const i=E.safeParse(JSON.parse(r.Schema));return i.success?c({cachedAt:new Date(this.now()).toISOString(),...r.LastUpdated!==void 0?{registryLastUpdated:r.LastUpdated.toISOString()}:{},summary:{typeName:e,createOnlyProperties:i.data.createOnlyProperties??[],conditionalCreateOnlyProperties:i.data.conditionalCreateOnlyProperties??[],primaryIdentifier:i.data.primaryIdentifier??[]}}):h(new Error(`Unparsable registry schema for ${e}: ${n(i.error.message)}`))}catch(s){return h(new Error(`DescribeType failed for ${e}: ${n(f(s))}`))}}async readCache(e){const t=this.cachePath(e);try{const s=await w(t,"utf8"),r=v.safeParse(JSON.parse(s));if(!r.success){o.debug(d,"Discarding unparsable registry cache entry",{path:t,error:n(r.error.message)});return}return r.data}catch(s){o.debug(d,"Registry cache read missed",{path:t,error:n(f(s))});return}}async writeCache(e,t){const s=this.cachePath(e);try{await p(u(this.cacheDir,this.aws.getRegion()),{recursive:!0});const r=`${s}.tmp-${process.pid}`;await b(r,JSON.stringify(t,null,2),"utf8"),await S(r,s)}catch(r){o.debug(d,"Registry cache write failed",{path:s,error:n(f(r))})}}}export{Y as CfnRegistryService};
@@ -1 +1 @@
1
- import{CloudFormationClient as m,DeleteStackCommand as A,DescribeStacksCommand as S,GetTemplateCommand as $,ListExportsCommand as O,UpdateStackCommand as P}from"@aws-sdk/client-cloudformation";import{stackStatusMap as C}from"../../aws/utils/stackStatus.js";import{maskSensitiveOutput as E}from"@fjall/util";import{success as d,failure as u}from"@fjall/generator";import{BaseServiceError as M}from"../../types/errors/ServiceError.js";import{logger as F}from"@fjall/util/logger";import{getErrorMessage as g,sleep as h}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as y}from"@fjall/util/aws";import{isCleanableState as R}from"../../types/constants.js";import{composeSdkAbortSignal as p,extractErrorName as D,isAborted as x}from"../../aws/organisations/types.js";class f extends M{errorType;stackName;stackStatus;constructor(e,s,r,t,a,n=!1){super(`CFN_${s.toUpperCase()}`,e,a,n),this.errorType=s,this.stackName=r,this.stackStatus=t}}class Y{aws;constructor(e){this.aws=e}classifyAwsError(e,s,r){const t=D(e),a=E(g(e));return t==="CredentialsError"||t==="UnauthorizedError"?new f(`AWS credentials error: ${a}`,"auth_error",r,void 0,e,!1):t==="Throttling"||t==="TooManyRequestsException"?new f(`AWS rate limit exceeded: ${a}`,"throttled",r,void 0,e,!0):t==="TimeoutError"?new f(`AWS request timed out: ${a}`,"timeout",r,void 0,e,!0):t==="NetworkingError"||t==="ENOTFOUND"?new f(`Network error: ${a}`,"network_error",r,void 0,e,!0):new f(`${s}: ${a}`,"unknown",r,void 0,e)}async getStackOutputs(e,s,r){s?.onStackCheck?.(e);const t=this.aws.getClient(m),a=new S({StackName:e});try{const o=(await t.send(a,{abortSignal:p(r)})).Stacks?.[0];if(!o?.Outputs)return d([]);const c=o.Outputs.map(l=>({OutputKey:l.OutputKey,OutputValue:l.OutputValue,ExportName:l.ExportName}));return s?.onOutputsRetrieved?.(e,c.length),d(c)}catch(n){if(n instanceof Error&&n.name==="ValidationError"&&n.message?.includes(y))return s?.onStackNotFound?.(e),d([]);const o=E(g(n));return u(new f(`Failed to get outputs for stack ${e}: ${o}`,"unknown",e,void 0,n))}}async getStackStatus(e,s,r){s?.onStackCheck?.(e);const t=this.aws.getClient(m),a=new S({StackName:e});try{const o=(await t.send(a,{abortSignal:p(r)})).Stacks?.[0];if(!o)return d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"});const c=o.StackStatus||"UNKNOWN",l=C[c]||C.UNKNOWN;return s?.onStackFound?.(e,c),d({status:c,safeToRedeploy:l.safeToRedeploy,description:l.description,statusReason:o.StackStatusReason})}catch(n){return n instanceof Error&&n.name==="ValidationError"&&n.message?.includes(y)?d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"}):u(this.classifyAwsError(n,`Failed to get stack status for ${e}`,e))}}async listAllExports(e,s){const r=this.aws.getClient(m),t=[];try{let a;do{if(x(s))break;const n=new O({NextToken:a}),o=await r.send(n,{abortSignal:p(s)}),c=o.Exports||[];for(const l of c)l.Name&&l.Value&&t.push({Name:l.Name,Value:l.Value});if(e?.(c))break;a=o.NextToken}while(a);return d(t)}catch(a){const n=E(g(a));return u(new f(`Failed to list exports: ${n}`,"unknown",void 0,void 0,a,!1))}}async getExportsByNames(e,s){if(e.length===0)return d(new Map);const r=new Set(e),t=new Map,a=await this.listAllExports(n=>{for(const o of n)o.Name&&r.has(o.Name)&&o.Value&&t.set(o.Name,o.Value);return t.size>=r.size},s);return a.success?d(t):u(a.error)}async listExports(e,s){const r=await this.listAllExports(void 0,s);return r.success&&e?.onExportsRetrieved?.(r.data.length),r}async deleteStack(e,s){const r=this.aws.getClient(m);try{return await r.send(new A({StackName:e}),{abortSignal:p(s)}),d(void 0)}catch(t){return u(this.classifyAwsError(t,`Failed to delete stack ${e}`,e))}}async stackExists(e,s,r){const t=s??this.aws.getClient(m);try{const n=(await t.send(new S({StackName:e}),{abortSignal:p(r)})).Stacks?.[0]?.StackStatus;return!!n&&n!=="REVIEW_IN_PROGRESS"&&!R(n)}catch(a){return a instanceof Error&&a.message?.includes(y)?!1:(F.debug("CloudFormationService","Error checking stack existence, assuming exists",{stackName:e,error:g(a)}),!0)}}async getTemplate(e,s){const r=this.aws.getClient(m);try{const t=await r.send(new $({StackName:e,TemplateStage:"Original"}),{abortSignal:p(s)});return d(t.TemplateBody??"")}catch(t){return t instanceof Error&&t.message?.includes(y)?u(new f(`Stack ${e} not found`,"stack_not_found",e)):u(this.classifyAwsError(t,`Failed to get template for stack ${e}`,e))}}async waitForDeleteComplete(e,s){const r=s?.timeoutMs??6e5,t=s?.pollIntervalMs??5e3,a=Date.now();for(;Date.now()-a<r;){const n=await this.getStackStatus(e,void 0,s?.abortSignal);if(!n.success){if(n.error.recoverable){s?.onProgress?.(`Transient error polling stack ${e}, retrying: ${E(n.error.message)}`),await h(t);continue}return u(n.error)}const o=n.data?.status;if(o==="DELETE_COMPLETE"||o==="DOES_NOT_EXIST")return d(void 0);if(o==="DELETE_FAILED")return u(new f(`Stack ${e} deletion failed: ${n.data?.statusReason||"unknown reason"}`,"stack_failed",e,o,void 0,!1));s?.onProgress?.(`Stack ${e} status: ${o??"unknown"}`),await h(t)}return u(new f(`Timed out waiting for stack ${e} deletion after ${Math.round(r/1e3)}s`,"timeout",e,void 0,void 0,!0))}async updateStackParameters(e,s,r){const t=this.aws.getClient(m);let a;try{a=await t.send(new S({StackName:e}),{abortSignal:p(r)})}catch(i){return u(this.classifyAwsError(i,`Failed to describe stack ${e} before parameter reconcile`,e))}const n=a.Stacks?.[0];if(!n)return u(new f(`Stack ${e} not found`,"stack_not_found",e));const o=n.Parameters??[],c=new Set(o.map(i=>i.ParameterKey).filter(i=>i!==void 0)),l=Object.entries(s),k=l.filter(([i])=>!c.has(i)).map(([i])=>i),T=new Map(l.filter(([i])=>c.has(i)));if(T.size===0)return d({updated:!1,skippedKeys:k});const _=o.filter(i=>i.ParameterKey!==void 0).map(i=>T.has(i.ParameterKey)?{ParameterKey:i.ParameterKey,ParameterValue:T.get(i.ParameterKey)}:{ParameterKey:i.ParameterKey,UsePreviousValue:!0});try{await t.send(new P({StackName:e,UsePreviousTemplate:!0,Parameters:_,Capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"]}),{abortSignal:p(r)})}catch(i){return i instanceof Error&&i.name==="ValidationError"&&i.message?.includes("No updates are to be performed")?d({updated:!1,skippedKeys:k}):u(this.classifyAwsError(i,`Failed to update parameters for stack ${e}`,e))}return this.waitForUpdateStackParametersComplete(e,k,r)}async waitForUpdateStackParametersComplete(e,s,r){const n=Date.now();for(;Date.now()-n<6e5;){if(x(r))return u(new f(`Aborted waiting for stack ${e} parameter update`,"timeout",e,void 0,void 0,!0));const o=await this.getStackStatus(e,void 0,r);if(!o.success){if(o.error.recoverable){await h(5e3);continue}return u(o.error)}const c=o.data?.status??"UNKNOWN";if(c==="UPDATE_COMPLETE")return d({updated:!0,skippedKeys:s});if(I(c))return u(new f(`Stack ${e} parameter update failed: ${o.data?.statusReason||"unknown reason"}`,"stack_failed",e,c,void 0,!1));await h(5e3)}return u(new f(`Timed out waiting for stack ${e} parameter update after ${Math.round(6e5/1e3)}s`,"timeout",e,void 0,void 0,!0))}}function I(w){return w.endsWith("_IN_PROGRESS")?!1:w.startsWith("UPDATE_ROLLBACK_")||w==="UPDATE_FAILED"}export{f as CloudFormationError,Y as CloudFormationService};
1
+ var $=Object.defineProperty;var S=(m,e)=>$(m,"name",{value:e,configurable:!0});import{CloudFormationClient as w,DeleteStackCommand as O,DescribeStacksCommand as E,GetTemplateCommand as P,ListExportsCommand as M,UpdateStackCommand as F}from"@aws-sdk/client-cloudformation";import{stackStatusMap as x}from"../../aws/utils/stackStatus.js";import{maskSensitiveOutput as g}from"@fjall/util";import{success as d,failure as u}from"@fjall/generator";import{BaseServiceError as R}from"../../types/errors/ServiceError.js";import{logger as D}from"@fjall/util/logger";import{getErrorMessage as h,sleep as y}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as k}from"@fjall/util/aws";import{isCleanableState as I}from"../../types/constants.js";import{composeSdkAbortSignal as p,extractErrorName as b,isAborted as _}from"../../aws/organisations/types.js";class f extends R{static{S(this,"CloudFormationError")}errorType;stackName;stackStatus;constructor(e,s,r,t,a,n=!1){super(`CFN_${s.toUpperCase()}`,e,a,n),this.errorType=s,this.stackName=r,this.stackStatus=t}}class q{static{S(this,"CloudFormationService")}aws;constructor(e){this.aws=e}classifyAwsError(e,s,r){const t=b(e),a=g(h(e));return t==="CredentialsError"||t==="UnauthorizedError"?new f(`AWS credentials error: ${a}`,"auth_error",r,void 0,e,!1):t==="Throttling"||t==="TooManyRequestsException"?new f(`AWS rate limit exceeded: ${a}`,"throttled",r,void 0,e,!0):t==="TimeoutError"?new f(`AWS request timed out: ${a}`,"timeout",r,void 0,e,!0):t==="NetworkingError"||t==="ENOTFOUND"?new f(`Network error: ${a}`,"network_error",r,void 0,e,!0):new f(`${s}: ${a}`,"unknown",r,void 0,e)}async getStackOutputs(e,s,r){s?.onStackCheck?.(e);const t=this.aws.getClient(w),a=new E({StackName:e});try{const o=(await t.send(a,{abortSignal:p(r)})).Stacks?.[0];if(!o?.Outputs)return d([]);const c=o.Outputs.map(l=>({OutputKey:l.OutputKey,OutputValue:l.OutputValue,ExportName:l.ExportName}));return s?.onOutputsRetrieved?.(e,c.length),d(c)}catch(n){if(n instanceof Error&&n.name==="ValidationError"&&n.message?.includes(k))return s?.onStackNotFound?.(e),d([]);const o=g(h(n));return u(new f(`Failed to get outputs for stack ${e}: ${o}`,"unknown",e,void 0,n))}}async getStackStatus(e,s,r){s?.onStackCheck?.(e);const t=this.aws.getClient(w),a=new E({StackName:e});try{const o=(await t.send(a,{abortSignal:p(r)})).Stacks?.[0];if(!o)return d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"});const c=o.StackStatus||"UNKNOWN",l=x[c]||x.UNKNOWN;return s?.onStackFound?.(e,c),d({status:c,safeToRedeploy:l.safeToRedeploy,description:l.description,statusReason:o.StackStatusReason})}catch(n){return n instanceof Error&&n.name==="ValidationError"&&n.message?.includes(k)?d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"}):u(this.classifyAwsError(n,`Failed to get stack status for ${e}`,e))}}async listAllExports(e,s){const r=this.aws.getClient(w),t=[];try{let a;do{if(_(s))break;const n=new M({NextToken:a}),o=await r.send(n,{abortSignal:p(s)}),c=o.Exports||[];for(const l of c)l.Name&&l.Value&&t.push({Name:l.Name,Value:l.Value});if(e?.(c))break;a=o.NextToken}while(a);return d(t)}catch(a){const n=g(h(a));return u(new f(`Failed to list exports: ${n}`,"unknown",void 0,void 0,a,!1))}}async getExportsByNames(e,s){if(e.length===0)return d(new Map);const r=new Set(e),t=new Map,a=await this.listAllExports(n=>{for(const o of n)o.Name&&r.has(o.Name)&&o.Value&&t.set(o.Name,o.Value);return t.size>=r.size},s);return a.success?d(t):u(a.error)}async listExports(e,s){const r=await this.listAllExports(void 0,s);return r.success&&e?.onExportsRetrieved?.(r.data.length),r}async deleteStack(e,s){const r=this.aws.getClient(w);try{return await r.send(new O({StackName:e}),{abortSignal:p(s)}),d(void 0)}catch(t){return u(this.classifyAwsError(t,`Failed to delete stack ${e}`,e))}}async stackExists(e,s,r){const t=s??this.aws.getClient(w);try{const n=(await t.send(new E({StackName:e}),{abortSignal:p(r)})).Stacks?.[0]?.StackStatus;return!!n&&n!=="REVIEW_IN_PROGRESS"&&!I(n)}catch(a){return a instanceof Error&&a.message?.includes(k)?!1:(D.debug("CloudFormationService","Error checking stack existence, assuming exists",{stackName:e,error:h(a)}),!0)}}async getTemplate(e,s){const r=this.aws.getClient(w);try{const t=await r.send(new P({StackName:e,TemplateStage:"Original"}),{abortSignal:p(s)});return d(t.TemplateBody??"")}catch(t){return t instanceof Error&&t.message?.includes(k)?u(new f(`Stack ${e} not found`,"stack_not_found",e)):u(this.classifyAwsError(t,`Failed to get template for stack ${e}`,e))}}async waitForDeleteComplete(e,s){const r=s?.timeoutMs??6e5,t=s?.pollIntervalMs??5e3,a=Date.now();for(;Date.now()-a<r;){const n=await this.getStackStatus(e,void 0,s?.abortSignal);if(!n.success){if(n.error.recoverable){s?.onProgress?.(`Transient error polling stack ${e}, retrying: ${g(n.error.message)}`),await y(t);continue}return u(n.error)}const o=n.data?.status;if(o==="DELETE_COMPLETE"||o==="DOES_NOT_EXIST")return d(void 0);if(o==="DELETE_FAILED")return u(new f(`Stack ${e} deletion failed: ${n.data?.statusReason||"unknown reason"}`,"stack_failed",e,o,void 0,!1));s?.onProgress?.(`Stack ${e} status: ${o??"unknown"}`),await y(t)}return u(new f(`Timed out waiting for stack ${e} deletion after ${Math.round(r/1e3)}s`,"timeout",e,void 0,void 0,!0))}async updateStackParameters(e,s,r){const t=this.aws.getClient(w);let a;try{a=await t.send(new E({StackName:e}),{abortSignal:p(r)})}catch(i){return u(this.classifyAwsError(i,`Failed to describe stack ${e} before parameter reconcile`,e))}const n=a.Stacks?.[0];if(!n)return u(new f(`Stack ${e} not found`,"stack_not_found",e));const o=n.Parameters??[],c=new Set(o.map(i=>i.ParameterKey).filter(i=>i!==void 0)),l=Object.entries(s),T=l.filter(([i])=>!c.has(i)).map(([i])=>i),C=new Map(l.filter(([i])=>c.has(i)));if(C.size===0)return d({updated:!1,skippedKeys:T});const A=o.filter(i=>i.ParameterKey!==void 0).map(i=>C.has(i.ParameterKey)?{ParameterKey:i.ParameterKey,ParameterValue:C.get(i.ParameterKey)}:{ParameterKey:i.ParameterKey,UsePreviousValue:!0});try{await t.send(new F({StackName:e,UsePreviousTemplate:!0,Parameters:A,Capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"]}),{abortSignal:p(r)})}catch(i){return i instanceof Error&&i.name==="ValidationError"&&i.message?.includes("No updates are to be performed")?d({updated:!1,skippedKeys:T}):u(this.classifyAwsError(i,`Failed to update parameters for stack ${e}`,e))}return this.waitForUpdateStackParametersComplete(e,T,r)}async waitForUpdateStackParametersComplete(e,s,r){const n=Date.now();for(;Date.now()-n<6e5;){if(_(r))return u(new f(`Aborted waiting for stack ${e} parameter update`,"timeout",e,void 0,void 0,!0));const o=await this.getStackStatus(e,void 0,r);if(!o.success){if(o.error.recoverable){await y(5e3);continue}return u(o.error)}const c=o.data?.status??"UNKNOWN";if(c==="UPDATE_COMPLETE")return d({updated:!0,skippedKeys:s});if(v(c))return u(new f(`Stack ${e} parameter update failed: ${o.data?.statusReason||"unknown reason"}`,"stack_failed",e,c,void 0,!1));await y(5e3)}return u(new f(`Timed out waiting for stack ${e} parameter update after ${Math.round(6e5/1e3)}s`,"timeout",e,void 0,void 0,!0))}}function v(m){return m.endsWith("_IN_PROGRESS")?!1:m.startsWith("UPDATE_ROLLBACK_")||m==="UPDATE_FAILED"}S(v,"isUpdateStackTerminalFailure");export{f as CloudFormationError,q as CloudFormationService};
@@ -1 +1 @@
1
- import{ECRClient as m,BatchGetImageCommand as h,DescribeImagesCommand as k}from"@aws-sdk/client-ecr";import{success as o,failure as c}from"@fjall/generator";import{logger as f}from"@fjall/util/logger";import{getErrorMessage as p}from"@fjall/util";import{composeSdkAbortSignal as I}from"../../aws/organisations/types.js";const E="EcrImageInspector",C=/^(\d{12})\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com\/(.+)$/;function $(n){const r=n.lastIndexOf("@");if(r>0)return n.slice(0,r);const e=n.lastIndexOf(":");return e>0?n.slice(0,e):n}class G{awsProvider;constructor(r){this.awsProvider=r}async getImageDigest(r,e,d){const s=$(r).match(C);if(!s)return f.debug(E,"Image URI is not ECR \u2014 skipping digest lookup",{imageUri:r}),o(void 0);const[,,,t]=s;if(!t)return o(void 0);try{const u=await this.awsProvider.getClient(m).send(new h({repositoryName:t,imageIds:[{imageTag:e}]}),{abortSignal:I(d)}),a=u.failures??[];if(a.length>0){const R=a.map(l=>`${l.failureCode??"unknown"}:${l.failureReason??""}`).join("; ");return c(new Error(`ECR BatchGetImage rejected ${t}:${e} \u2014 ${R}`))}const g=u.images?.[0]?.imageId?.imageDigest;return g?o(g):c(new Error(`ECR BatchGetImage returned no digest for ${t}:${e}`))}catch(i){return c(new Error(`ECR digest lookup failed for ${t}:${e} \u2014 ${p(i)}`))}}async getImageTagsByDigest(r,e,d){const s=$(r).match(C);if(!s)return f.debug(E,"Image URI is not ECR \u2014 skipping tag lookup by digest",{imageUri:r}),o(void 0);const[,,,t]=s;if(!t)return o(void 0);try{const a=(await this.awsProvider.getClient(m).send(new k({repositoryName:t,imageIds:[{imageDigest:e}]}),{abortSignal:I(d)})).imageDetails?.[0];return a?o(a.imageTags??[]):c(new Error(`ECR DescribeImages returned no image for ${t}@${e}`))}catch(i){return c(new Error(`ECR tag lookup failed for ${t}@${e} \u2014 ${p(i)}`))}}}export{G as EcrImageInspectorService,$ as stripTagOrDigest};
1
+ var k=Object.defineProperty;var g=(n,e)=>k(n,"name",{value:e,configurable:!0});import{ECRClient as f,BatchGetImageCommand as b,DescribeImagesCommand as y}from"@aws-sdk/client-ecr";import{success as o,failure as c}from"@fjall/generator";import{logger as p}from"@fjall/util/logger";import{getErrorMessage as I}from"@fjall/util";import{composeSdkAbortSignal as E}from"../../aws/organisations/types.js";const C="EcrImageInspector",$=/^(\d{12})\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com\/(.+)$/;function w(n){const e=n.lastIndexOf("@");if(e>0)return n.slice(0,e);const r=n.lastIndexOf(":");return r>0?n.slice(0,r):n}g(w,"stripTagOrDigest");class S{static{g(this,"EcrImageInspectorService")}awsProvider;constructor(e){this.awsProvider=e}async getImageDigest(e,r,d){const s=w(e).match($);if(!s)return p.debug(C,"Image URI is not ECR \u2014 skipping digest lookup",{imageUri:e}),o(void 0);const[,,,t]=s;if(!t)return o(void 0);try{const u=await this.awsProvider.getClient(f).send(new b({repositoryName:t,imageIds:[{imageTag:r}]}),{abortSignal:E(d)}),a=u.failures??[];if(a.length>0){const h=a.map(m=>`${m.failureCode??"unknown"}:${m.failureReason??""}`).join("; ");return c(new Error(`ECR BatchGetImage rejected ${t}:${r} \u2014 ${h}`))}const l=u.images?.[0]?.imageId?.imageDigest;return l?o(l):c(new Error(`ECR BatchGetImage returned no digest for ${t}:${r}`))}catch(i){return c(new Error(`ECR digest lookup failed for ${t}:${r} \u2014 ${I(i)}`))}}async getImageTagsByDigest(e,r,d){const s=w(e).match($);if(!s)return p.debug(C,"Image URI is not ECR \u2014 skipping tag lookup by digest",{imageUri:e}),o(void 0);const[,,,t]=s;if(!t)return o(void 0);try{const a=(await this.awsProvider.getClient(f).send(new y({repositoryName:t,imageIds:[{imageDigest:r}]}),{abortSignal:E(d)})).imageDetails?.[0];return a?o(a.imageTags??[]):c(new Error(`ECR DescribeImages returned no image for ${t}@${r}`))}catch(i){return c(new Error(`ECR tag lookup failed for ${t}@${r} \u2014 ${I(i)}`))}}}export{S as EcrImageInspectorService,w as stripTagOrDigest};
@@ -1 +1 @@
1
- import{ECSClient as $,UpdateServiceCommand as T,DescribeServicesCommand as b,DescribeClustersCommand as F,ListServicesCommand as R,ListClustersCommand as M,RegisterTaskDefinitionCommand as N,DescribeTaskDefinitionCommand as _}from"@aws-sdk/client-ecs";import{success as c,failure as l}from"@fjall/generator";import{BaseServiceError as I}from"../../types/errors/ServiceError.js";import{logger as D}from"@fjall/util/logger";import{getErrorMessage as w,maskSensitiveOutput as p,sleep as U}from"@fjall/util";import{getAwsErrorDetails as x}from"../../aws/utils/awsErrorHandler.js";import{composeSdkAbortSignal as g,isAborted as A}from"../../aws/organisations/types.js";class m extends I{errorType;resourceArn;constructor(n,s,e,t,r=!1){super(`ECS_${s.toUpperCase()}`,n,t,r),this.errorType=s,this.resourceArn=e}}class W{aws;constructor(n){this.aws=n}getClient(){return this.aws.getClient($)}async updateService(n,s,e,t){try{const r=this.getClient(),a=new T({cluster:n,service:s,forceNewDeployment:e?.forceNewDeployment!==!1,desiredCount:e?.desiredCount,taskDefinition:e?.taskDefinition});return D.debug("EcsService",`UpdateService request: cluster=${n}, service=${s}, forceNewDeployment=${e?.forceNewDeployment!==!1}, taskDefinition=${e?.taskDefinition??"<unchanged>"}`),await r.send(a,{abortSignal:g(t)}),c(void 0)}catch(r){const{message:a,name:o,httpCode:i}=x(r),d=p(a);D.error("EcsService",`UpdateService failed: ${o} - ${d}`,{clusterArn:n,serviceArn:s,httpCode:i,error:r});let f=`Failed to update ECS service: ${d}`;return i&&(f+=` (HTTP ${i})`),o!=="Error"&&o!=="Unknown"&&(f+=` [${o}]`),l(new m(f,"unknown",s,{clusterArn:n,error:r,httpCode:i,errorName:o},!1))}}async describeServices(n,s,e){try{const t=this.getClient(),r=new b({cluster:n,services:s,include:["TAGS"]}),a=await t.send(r,{abortSignal:g(e)});if(!a.services)return c([]);const o=a.services.map(i=>({serviceArn:i.serviceArn,serviceName:i.serviceName,clusterArn:i.clusterArn,status:i.status,desiredCount:i.desiredCount,runningCount:i.runningCount,pendingCount:i.pendingCount,deployments:i.deployments,events:i.events,taskDefinition:i.taskDefinition}));return c(o)}catch(t){const r=p(w(t));return l(new m(`Failed to describe services: ${r}`,"unknown",void 0,{clusterArn:n,serviceArns:s,error:t},!1))}}async getDeploymentStatus(n,s,e){const t=await this.describeServices(n,[s],e);if(!t.success)return l(t.error);const r=t.data;if(r.length===0)return l(new m(`Service ${s} not found in cluster ${n}`,"service_not_found",s,{clusterArn:n}));const a=r[0],o=a.deployments||[],i=o.find(C=>C.status==="PRIMARY");if(!i)return c({deploymentCompleted:!1,deploymentFailed:!0,runningCount:0,desiredCount:0,pendingCount:0,deploymentCount:o.length,message:"No primary deployment found",percentComplete:0});const d=i.desiredCount||0,f=i.runningCount||0,y=i.pendingCount||0,v=d>0?Math.round(f/d*100):0,h=o.length===1&&f===d&&y===0,k=(i.failedTasks??0)>0||i.rolloutState==="FAILED";let u=`Running: ${f}/${d}`;if(u+=`, Pending: ${y}`,o.length>1&&(u+=` (${o.length} deployments active)`),k){const C=i.rolloutStateReason||(i.failedTasks?`${i.failedTasks} task(s) failed to start`:void 0);C&&(u+=` \u2014 ${C}`)}const S=a.events&&a.events.length>0?a.events[0]?.message:void 0;return c({deploymentCompleted:h,deploymentFailed:k,runningCount:f,desiredCount:d,pendingCount:y,deploymentCount:o.length,message:u,percentComplete:v,latestEvent:S})}async isDeploying(n,s){const e=await this.getDeploymentStatus(n,s);if(!e.success)return D.debug("EcsService","Could not determine deployment status, assuming not deploying",{error:e.error.message}),c(!1);const t=e.data;return c(!t.deploymentCompleted&&!t.deploymentFailed)}async getServiceEvents(n,s,e){try{const t=this.getClient(),r=new b({cluster:n,services:[s],include:["TAGS"]}),a=await t.send(r,{abortSignal:g(e)});if(!a.services||a.services.length===0)return c([]);const i=(a.services[0].events||[]).map(d=>({message:d.message||"",timestamp:d.createdAt||new Date}));return c(i)}catch(t){const r=p(w(t));return l(new m(`Failed to get service events: ${r}`,"unknown",s,{clusterArn:n,error:t}))}}async pollDeployment(n){const{clusterArn:s,serviceArn:e,waitForCompletion:t=!0,maxWaitTime:r=6e5,pollInterval:a=5e3,progressCallback:o,abortSignal:i}=n;if(!t)return c(void 0);const d=Date.now(),f=new Date(d-5e3);let y=null,v="";const h=new Set;for(;Date.now()-d<r;){if(A(i))return l(new m("Deployment polling cancelled by abort signal","unknown",e,{clusterArn:s}));const k=await this.getDeploymentStatus(s,e,i);if(!k.success)return l(k.error);const u=k.data,S=await this.getServiceEvents(s,e,i);if(S.success){const C=this.processDeploymentEvents(S.data,u,f,y,h,o);y=C.lastEventTimestamp,C.eventsReported?v=u.message:u.message!==v&&(o?.(u),v=u.message)}if(u.deploymentCompleted)return c(void 0);if(u.deploymentFailed)return l(new m("ECS deployment failed. Check the ECS console for detailed error information","deployment_failed",e,{clusterArn:s,status:u}));await U(a)}return l(new m(`ECS deployment timed out after ${r/1e3} seconds. The deployment is still in progress. Check the ECS console for status`,"deployment_timeout",e,{clusterArn:s,maxWaitTime:r}))}processDeploymentEvents(n,s,e,t,r,a){const o=n.filter(i=>!(i.timestamp<e||t&&i.timestamp<=t||r.has(i.message)));if(o.length===0)return{lastEventTimestamp:t,eventsReported:!1};for(const i of o)r.add(i.message),a?.({...s,latestEvent:i.message});return{lastEventTimestamp:o[o.length-1].timestamp,eventsReported:!0}}async listClusters(n){try{const s=this.getClient(),e=new M({}),t=await s.send(e,{abortSignal:g(n)});return c(t.clusterArns||[])}catch(s){const e=p(w(s));return l(new m(`Failed to list clusters: ${e}`,"permission_denied",void 0,s))}}async describeClusters(n,s){try{const e=this.getClient(),t=new F({clusters:n}),r=await e.send(t,{abortSignal:g(s)});if(!r.clusters)return c([]);const a=r.clusters.map(o=>({clusterArn:o.clusterArn,clusterName:o.clusterName,status:o.status,registeredContainerInstancesCount:o.registeredContainerInstancesCount,runningTasksCount:o.runningTasksCount,pendingTasksCount:o.pendingTasksCount}));return c(a)}catch(e){const t=p(w(e));return l(new m(`Failed to describe clusters: ${t}`,"unknown",void 0,{clusterArns:n,error:e},!1))}}async listServices(n,s){try{const e=this.getClient(),t=new R({cluster:n}),r=await e.send(t,{abortSignal:g(s)});return c(r.serviceArns||[])}catch(e){const t=p(w(e));return l(new m(`Failed to list services: ${t}`,"unknown",n,{clusterArn:n,error:e},!1))}}async registerTaskDefinition(n,s){try{const e=this.getClient(),t=new N(n),r=await e.send(t,{abortSignal:g(s)});return r.taskDefinition?.taskDefinitionArn?c(r.taskDefinition.taskDefinitionArn):l(new m("Task definition registration failed - no ARN returned","task_definition_failed",void 0,n))}catch(e){const t=p(w(e));return l(new m(`Failed to register task definition: ${t}`,"task_definition_failed",void 0,{taskDefinition:n,error:e},!1))}}async getLatestTaskDefinition(n,s){try{const e=this.getClient(),t=new _({taskDefinition:n}),r=await e.send(t,{abortSignal:g(s)});return c(r.taskDefinition)}catch(e){const t=p(w(e));return l(new m(`Failed to get task definition: ${t}`,"unknown",void 0,{family:n,error:e},!1))}}async deployService(n,s,e){const t=await this.updateService(n,s,{forceNewDeployment:!0,taskDefinition:e?.taskDefinition},e?.abortSignal);if(!t.success)return l(t.error);if(e?.waitForCompletion!==!1){const r=await this.pollDeployment({clusterArn:n,serviceArn:s,waitForCompletion:!0,progressCallback:e?.progressCallback,abortSignal:e?.abortSignal});if(!r.success)return l(r.error)}return c(void 0)}}export{m as EcsError,W as EcsService};
1
+ var T=Object.defineProperty;var b=(h,n)=>T(h,"name",{value:n,configurable:!0});import{ECSClient as F,UpdateServiceCommand as R,DescribeServicesCommand as $,DescribeClustersCommand as M,ListServicesCommand as N,ListClustersCommand as _,RegisterTaskDefinitionCommand as I,DescribeTaskDefinitionCommand as U}from"@aws-sdk/client-ecs";import{success as c,failure as l}from"@fjall/generator";import{BaseServiceError as x}from"../../types/errors/ServiceError.js";import{logger as E}from"@fjall/util/logger";import{getErrorMessage as w,maskSensitiveOutput as p,sleep as A}from"@fjall/util";import{getAwsErrorDetails as L}from"../../aws/utils/awsErrorHandler.js";import{composeSdkAbortSignal as g,isAborted as P}from"../../aws/organisations/types.js";class m extends x{static{b(this,"EcsError")}errorType;resourceArn;constructor(n,s,e,t,r=!1){super(`ECS_${s.toUpperCase()}`,n,t,r),this.errorType=s,this.resourceArn=e}}class z{static{b(this,"EcsService")}aws;constructor(n){this.aws=n}getClient(){return this.aws.getClient(F)}async updateService(n,s,e,t){try{const r=this.getClient(),a=new R({cluster:n,service:s,forceNewDeployment:e?.forceNewDeployment!==!1,desiredCount:e?.desiredCount,taskDefinition:e?.taskDefinition});return E.debug("EcsService",`UpdateService request: cluster=${n}, service=${s}, forceNewDeployment=${e?.forceNewDeployment!==!1}, taskDefinition=${e?.taskDefinition??"<unchanged>"}`),await r.send(a,{abortSignal:g(t)}),c(void 0)}catch(r){const{message:a,name:o,httpCode:i}=L(r),d=p(a);E.error("EcsService",`UpdateService failed: ${o} - ${d}`,{clusterArn:n,serviceArn:s,httpCode:i,error:r});let f=`Failed to update ECS service: ${d}`;return i&&(f+=` (HTTP ${i})`),o!=="Error"&&o!=="Unknown"&&(f+=` [${o}]`),l(new m(f,"unknown",s,{clusterArn:n,error:r,httpCode:i,errorName:o},!1))}}async describeServices(n,s,e){try{const t=this.getClient(),r=new $({cluster:n,services:s,include:["TAGS"]}),a=await t.send(r,{abortSignal:g(e)});if(!a.services)return c([]);const o=a.services.map(i=>({serviceArn:i.serviceArn,serviceName:i.serviceName,clusterArn:i.clusterArn,status:i.status,desiredCount:i.desiredCount,runningCount:i.runningCount,pendingCount:i.pendingCount,deployments:i.deployments,events:i.events,taskDefinition:i.taskDefinition}));return c(o)}catch(t){const r=p(w(t));return l(new m(`Failed to describe services: ${r}`,"unknown",void 0,{clusterArn:n,serviceArns:s,error:t},!1))}}async getDeploymentStatus(n,s,e){const t=await this.describeServices(n,[s],e);if(!t.success)return l(t.error);const r=t.data;if(r.length===0)return l(new m(`Service ${s} not found in cluster ${n}`,"service_not_found",s,{clusterArn:n}));const a=r[0],o=a.deployments||[],i=o.find(C=>C.status==="PRIMARY");if(!i)return c({deploymentCompleted:!1,deploymentFailed:!0,runningCount:0,desiredCount:0,pendingCount:0,deploymentCount:o.length,message:"No primary deployment found",percentComplete:0});const d=i.desiredCount||0,f=i.runningCount||0,y=i.pendingCount||0,v=d>0?Math.round(f/d*100):0,D=o.length===1&&f===d&&y===0,k=(i.failedTasks??0)>0||i.rolloutState==="FAILED";let u=`Running: ${f}/${d}`;if(u+=`, Pending: ${y}`,o.length>1&&(u+=` (${o.length} deployments active)`),k){const C=i.rolloutStateReason||(i.failedTasks?`${i.failedTasks} task(s) failed to start`:void 0);C&&(u+=` \u2014 ${C}`)}const S=a.events&&a.events.length>0?a.events[0]?.message:void 0;return c({deploymentCompleted:D,deploymentFailed:k,runningCount:f,desiredCount:d,pendingCount:y,deploymentCount:o.length,message:u,percentComplete:v,latestEvent:S})}async isDeploying(n,s){const e=await this.getDeploymentStatus(n,s);if(!e.success)return E.debug("EcsService","Could not determine deployment status, assuming not deploying",{error:e.error.message}),c(!1);const t=e.data;return c(!t.deploymentCompleted&&!t.deploymentFailed)}async getServiceEvents(n,s,e){try{const t=this.getClient(),r=new $({cluster:n,services:[s],include:["TAGS"]}),a=await t.send(r,{abortSignal:g(e)});if(!a.services||a.services.length===0)return c([]);const i=(a.services[0].events||[]).map(d=>({message:d.message||"",timestamp:d.createdAt||new Date}));return c(i)}catch(t){const r=p(w(t));return l(new m(`Failed to get service events: ${r}`,"unknown",s,{clusterArn:n,error:t}))}}async pollDeployment(n){const{clusterArn:s,serviceArn:e,waitForCompletion:t=!0,maxWaitTime:r=6e5,pollInterval:a=5e3,progressCallback:o,abortSignal:i}=n;if(!t)return c(void 0);const d=Date.now(),f=new Date(d-5e3);let y=null,v="";const D=new Set;for(;Date.now()-d<r;){if(P(i))return l(new m("Deployment polling cancelled by abort signal","unknown",e,{clusterArn:s}));const k=await this.getDeploymentStatus(s,e,i);if(!k.success)return l(k.error);const u=k.data,S=await this.getServiceEvents(s,e,i);if(S.success){const C=this.processDeploymentEvents(S.data,u,f,y,D,o);y=C.lastEventTimestamp,C.eventsReported?v=u.message:u.message!==v&&(o?.(u),v=u.message)}if(u.deploymentCompleted)return c(void 0);if(u.deploymentFailed)return l(new m("ECS deployment failed. Check the ECS console for detailed error information","deployment_failed",e,{clusterArn:s,status:u}));await A(a)}return l(new m(`ECS deployment timed out after ${r/1e3} seconds. The deployment is still in progress. Check the ECS console for status`,"deployment_timeout",e,{clusterArn:s,maxWaitTime:r}))}processDeploymentEvents(n,s,e,t,r,a){const o=n.filter(i=>!(i.timestamp<e||t&&i.timestamp<=t||r.has(i.message)));if(o.length===0)return{lastEventTimestamp:t,eventsReported:!1};for(const i of o)r.add(i.message),a?.({...s,latestEvent:i.message});return{lastEventTimestamp:o[o.length-1].timestamp,eventsReported:!0}}async listClusters(n){try{const s=this.getClient(),e=new _({}),t=await s.send(e,{abortSignal:g(n)});return c(t.clusterArns||[])}catch(s){const e=p(w(s));return l(new m(`Failed to list clusters: ${e}`,"permission_denied",void 0,s))}}async describeClusters(n,s){try{const e=this.getClient(),t=new M({clusters:n}),r=await e.send(t,{abortSignal:g(s)});if(!r.clusters)return c([]);const a=r.clusters.map(o=>({clusterArn:o.clusterArn,clusterName:o.clusterName,status:o.status,registeredContainerInstancesCount:o.registeredContainerInstancesCount,runningTasksCount:o.runningTasksCount,pendingTasksCount:o.pendingTasksCount}));return c(a)}catch(e){const t=p(w(e));return l(new m(`Failed to describe clusters: ${t}`,"unknown",void 0,{clusterArns:n,error:e},!1))}}async listServices(n,s){try{const e=this.getClient(),t=new N({cluster:n}),r=await e.send(t,{abortSignal:g(s)});return c(r.serviceArns||[])}catch(e){const t=p(w(e));return l(new m(`Failed to list services: ${t}`,"unknown",n,{clusterArn:n,error:e},!1))}}async registerTaskDefinition(n,s){try{const e=this.getClient(),t=new I(n),r=await e.send(t,{abortSignal:g(s)});return r.taskDefinition?.taskDefinitionArn?c(r.taskDefinition.taskDefinitionArn):l(new m("Task definition registration failed - no ARN returned","task_definition_failed",void 0,n))}catch(e){const t=p(w(e));return l(new m(`Failed to register task definition: ${t}`,"task_definition_failed",void 0,{taskDefinition:n,error:e},!1))}}async getLatestTaskDefinition(n,s){try{const e=this.getClient(),t=new U({taskDefinition:n}),r=await e.send(t,{abortSignal:g(s)});return c(r.taskDefinition)}catch(e){const t=p(w(e));return l(new m(`Failed to get task definition: ${t}`,"unknown",void 0,{family:n,error:e},!1))}}async deployService(n,s,e){const t=await this.updateService(n,s,{forceNewDeployment:!0,taskDefinition:e?.taskDefinition},e?.abortSignal);if(!t.success)return l(t.error);if(e?.waitForCompletion!==!1){const r=await this.pollDeployment({clusterArn:n,serviceArn:s,waitForCompletion:!0,progressCallback:e?.progressCallback,abortSignal:e?.abortSignal});if(!r.success)return l(r.error)}return c(void 0)}}export{m as EcsError,z as EcsService};