@fjall/deploy-core 2.21.0 → 2.22.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 (82) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/AwsProvider.d.ts +9 -0
  3. package/dist/src/aws/index.d.ts +2 -0
  4. package/dist/src/aws/index.js +1 -1
  5. package/dist/src/aws/regions/regionOptStatus.d.ts +26 -0
  6. package/dist/src/aws/regions/regionOptStatus.js +1 -0
  7. package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -1
  8. package/dist/src/aws/utils/cloudformationEventTypes.d.ts +6 -0
  9. package/dist/src/aws/utils/cloudformationEvents.d.ts +1 -0
  10. package/dist/src/aws/utils/cloudformationEvents.js +1 -1
  11. package/dist/src/index.d.ts +10 -2
  12. package/dist/src/index.js +1 -1
  13. package/dist/src/orchestration/application/applicationDeploy.js +1 -1
  14. package/dist/src/orchestration/application/applicationDeployHelpers.js +2 -2
  15. package/dist/src/orchestration/application/approvalGate.d.ts +44 -0
  16. package/dist/src/orchestration/application/approvalGate.js +1 -0
  17. package/dist/src/orchestration/application/buildArgResolver.d.ts +72 -0
  18. package/dist/src/orchestration/application/buildArgResolver.js +1 -0
  19. package/dist/src/orchestration/application/buildGroupValidator.d.ts +64 -0
  20. package/dist/src/orchestration/application/buildGroupValidator.js +1 -0
  21. package/dist/src/orchestration/application/buildSecretResolver.d.ts +57 -0
  22. package/dist/src/orchestration/application/buildSecretResolver.js +1 -0
  23. package/dist/src/orchestration/application/buildSecretSession.d.ts +40 -0
  24. package/dist/src/orchestration/application/buildSecretSession.js +1 -0
  25. package/dist/src/orchestration/application/codeOnlyDeploy.js +1 -1
  26. package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
  27. package/dist/src/orchestration/application/plan/approvalToken.d.ts +40 -0
  28. package/dist/src/orchestration/application/plan/approvalToken.js +1 -0
  29. package/dist/src/orchestration/application/plan/assemblyDigest.d.ts +21 -0
  30. package/dist/src/orchestration/application/plan/assemblyDigest.js +2 -0
  31. package/dist/src/orchestration/application/plan/buildDeployPlan.d.ts +14 -0
  32. package/dist/src/orchestration/application/plan/buildDeployPlan.js +1 -0
  33. package/dist/src/orchestration/application/plan/classify.d.ts +37 -0
  34. package/dist/src/orchestration/application/plan/classify.js +1 -0
  35. package/dist/src/orchestration/application/plan/computeDeployPlan.d.ts +24 -0
  36. package/dist/src/orchestration/application/plan/computeDeployPlan.js +1 -0
  37. package/dist/src/orchestration/application/plan/index.d.ts +8 -0
  38. package/dist/src/orchestration/application/plan/index.js +1 -0
  39. package/dist/src/orchestration/application/plan/renderDeployPlan.d.ts +18 -0
  40. package/dist/src/orchestration/application/plan/renderDeployPlan.js +1 -0
  41. package/dist/src/orchestration/application/plan/types.d.ts +54 -0
  42. package/dist/src/orchestration/application/plan/types.js +0 -0
  43. package/dist/src/orchestration/deploy.js +1 -1
  44. package/dist/src/orchestration/destroy.js +1 -1
  45. package/dist/src/orchestration/dockerBuildParamsKeys.d.ts +2 -0
  46. package/dist/src/orchestration/dockerBuildParamsKeys.js +1 -0
  47. package/dist/src/orchestration/dockerInterface.d.ts +34 -0
  48. package/dist/src/orchestration/index.d.ts +8 -1
  49. package/dist/src/orchestration/index.js +1 -1
  50. package/dist/src/orchestration/serviceFactory.d.ts +7 -0
  51. package/dist/src/orchestration/serviceFactory.js +1 -1
  52. package/dist/src/orchestration/stepLifecycle.d.ts +7 -1
  53. package/dist/src/orchestration/stepLifecycle.js +1 -1
  54. package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +3 -0
  55. package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -1
  56. package/dist/src/services/infrastructure/CdkProcessManager.d.ts +10 -1
  57. package/dist/src/services/infrastructure/CdkProcessManager.js +3 -3
  58. package/dist/src/services/infrastructure/CdkService.d.ts +1 -0
  59. package/dist/src/services/infrastructure/CdkService.js +2 -2
  60. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +15 -0
  61. package/dist/src/services/infrastructure/CloudFormationService.d.ts +11 -0
  62. package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
  63. package/dist/src/services/infrastructure/cancelInFlightStack.d.ts +22 -0
  64. package/dist/src/services/infrastructure/cancelInFlightStack.js +3 -0
  65. package/dist/src/types/approval.d.ts +53 -0
  66. package/dist/src/types/approval.js +0 -0
  67. package/dist/src/types/callbackKeys.d.ts +1 -1
  68. package/dist/src/types/callbackKeys.js +1 -1
  69. package/dist/src/types/callbacks.d.ts +18 -4
  70. package/dist/src/types/config/entitlements.d.ts +7 -0
  71. package/dist/src/types/deployEmitter.d.ts +85 -0
  72. package/dist/src/types/deployEmitter.js +1 -0
  73. package/dist/src/types/deployEvent.d.ts +907 -0
  74. package/dist/src/types/deployEvent.js +1 -0
  75. package/dist/src/types/deployEventParse.d.ts +52 -0
  76. package/dist/src/types/deployEventParse.js +1 -0
  77. package/dist/src/types/deployEventTypeGuards.d.ts +10 -0
  78. package/dist/src/types/deployEventTypeGuards.js +1 -0
  79. package/dist/src/types/index.d.ts +6 -1
  80. package/dist/src/types/index.js +1 -1
  81. package/dist/src/types/params.d.ts +103 -0
  82. package/package.json +8 -4
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 153 files minified at 2026-06-27T21:57:29.114Z
1
+ 174 files minified at 2026-06-30T11:38:21.686Z
@@ -23,6 +23,15 @@ export interface AwsProvider {
23
23
  * Used by organisation cascade to assume roles in member accounts.
24
24
  */
25
25
  assumeRole?(roleArn: string, sessionName: string): Promise<AwsProviderCredentials>;
26
+ /**
27
+ * Assume the SAME identity but with an inline session policy that scopes it
28
+ * down to exactly the SSM / Secrets Manager parameters a build needs to read
29
+ * (least privilege). Returns a provider bound to the scoped session. When a
30
+ * provider cannot narrow its own session (the worker's pre-assumed OIDC
31
+ * credentials), this is absent and the caller falls back to the unscoped
32
+ * provider — see `resolveBuildSecrets`.
33
+ */
34
+ assumeScopedBuildSecretSession?(inlinePolicy: string, sessionName: string): Promise<AwsProvider>;
26
35
  }
27
36
  export interface AwsProviderCredentials {
28
37
  accessKeyId: string;
@@ -4,6 +4,8 @@ export { checkTargetReadiness, describeTargetReadinessReason, buildTargetUnready
4
4
  export type { TargetReadinessAccount, TargetReadinessClients, TargetReadinessReason, TargetReadinessVerdict } from "./targetReadiness.js";
5
5
  export { buildTargetSetUnreadyAdvisory } from "./targetSetAdvisory.js";
6
6
  export type { TargetSetUnreadyAdvisoryParams } from "./targetSetAdvisory.js";
7
+ export { checkRegionEnabled, regionDisabledMessage } from "./regions/regionOptStatus.js";
8
+ export type { RegionAvailability } from "./regions/regionOptStatus.js";
7
9
  export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, extractErrorName, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./organisations/index.js";
8
10
  export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./organisations/index.js";
9
11
  export { CloudFormationEventMonitor } from "./utils/cloudformationEvents.js";
@@ -1 +1 @@
1
- import{SimpleAwsProvider as a}from"./SimpleAwsProvider.js";import{checkTargetReadiness as s,describeTargetReadinessReason as o,buildTargetUnreadyAdvisory as n,probeAccountStackExists as i}from"./targetReadiness.js";import{buildTargetSetUnreadyAdvisory as l}from"./targetSetAdvisory.js";import{ensureOrganisationExists as A,describeOrganisation as u,enablePolicyTypes as S,enableServiceAccess as E,SERVICE_PRINCIPALS as b,enableRamSharing as g,activateTrustedAccess as p,enableIpamDelegatedAdmin as R,updateBackupGlobalSettings as m,listAccounts as C,findAccount as T,createAccount as I,ensureOrganisationalUnitsExist as x,placeAccountsInOUs as y,buildAccountToOUMap as O,activateCostAllocationTags as U,checkIdentityCentreStatus as f,extractErrorName as v,isOULeaf as P,registerSecurityDelegates as _,SECURITY_SERVICE_PRINCIPALS as k,enableCentralisedRootAccess as h,ROOT_ACCESS_FEATURES as L,RootAccessEnablementError as N}from"./organisations/index.js";import{CloudFormationEventMonitor as F}from"./utils/cloudformationEvents.js";export{F as CloudFormationEventMonitor,L as ROOT_ACCESS_FEATURES,N as RootAccessEnablementError,k as SECURITY_SERVICE_PRINCIPALS,b as SERVICE_PRINCIPALS,a as SimpleAwsProvider,U as activateCostAllocationTags,p as activateTrustedAccess,O as buildAccountToOUMap,l as buildTargetSetUnreadyAdvisory,n as buildTargetUnreadyAdvisory,f as checkIdentityCentreStatus,s as checkTargetReadiness,I as createAccount,u as describeOrganisation,o as describeTargetReadinessReason,h as enableCentralisedRootAccess,R as enableIpamDelegatedAdmin,S as enablePolicyTypes,g as enableRamSharing,E as enableServiceAccess,A as ensureOrganisationExists,x as ensureOrganisationalUnitsExist,v as extractErrorName,T as findAccount,P as isOULeaf,C as listAccounts,y as placeAccountsInOUs,i as probeAccountStackExists,_ as registerSecurityDelegates,m as updateBackupGlobalSettings};
1
+ import{SimpleAwsProvider as a}from"./SimpleAwsProvider.js";import{checkTargetReadiness as s,describeTargetReadinessReason as o,buildTargetUnreadyAdvisory as n,probeAccountStackExists as i}from"./targetReadiness.js";import{buildTargetSetUnreadyAdvisory as l}from"./targetSetAdvisory.js";import{checkRegionEnabled as A,regionDisabledMessage as u}from"./regions/regionOptStatus.js";import{ensureOrganisationExists as E,describeOrganisation as S,enablePolicyTypes as b,enableServiceAccess as p,SERVICE_PRINCIPALS as R,enableRamSharing as m,activateTrustedAccess as C,enableIpamDelegatedAdmin as T,updateBackupGlobalSettings as x,listAccounts as I,findAccount as f,createAccount as y,ensureOrganisationalUnitsExist as O,placeAccountsInOUs as U,buildAccountToOUMap as v,activateCostAllocationTags as P,checkIdentityCentreStatus as k,extractErrorName as _,isOULeaf as h,registerSecurityDelegates as D,SECURITY_SERVICE_PRINCIPALS as L,enableCentralisedRootAccess as M,ROOT_ACCESS_FEATURES as N,RootAccessEnablementError as F}from"./organisations/index.js";import{CloudFormationEventMonitor as w}from"./utils/cloudformationEvents.js";export{w as CloudFormationEventMonitor,N as ROOT_ACCESS_FEATURES,F as RootAccessEnablementError,L as SECURITY_SERVICE_PRINCIPALS,R as SERVICE_PRINCIPALS,a as SimpleAwsProvider,P as activateCostAllocationTags,C as activateTrustedAccess,v as buildAccountToOUMap,l as buildTargetSetUnreadyAdvisory,n as buildTargetUnreadyAdvisory,k as checkIdentityCentreStatus,A as checkRegionEnabled,s as checkTargetReadiness,y as createAccount,S as describeOrganisation,o as describeTargetReadinessReason,M as enableCentralisedRootAccess,T as enableIpamDelegatedAdmin,b as enablePolicyTypes,m as enableRamSharing,p as enableServiceAccess,E as ensureOrganisationExists,O as ensureOrganisationalUnitsExist,_ as extractErrorName,f as findAccount,h as isOULeaf,I as listAccounts,U as placeAccountsInOUs,i as probeAccountStackExists,u as regionDisabledMessage,D as registerSecurityDelegates,x as updateBackupGlobalSettings};
@@ -0,0 +1,26 @@
1
+ import type { AwsProviderCredentials } from "../AwsProvider.js";
2
+ /**
3
+ * Verdict for a deploy region's opt-in status.
4
+ *
5
+ * `unknown` is the fail-open outcome: the Account API was unreachable, denied,
6
+ * throttled, or timed out, so the caller proceeds — a pre-flight must never
7
+ * block a deploy on its own probe. The real error then surfaces at
8
+ * synth/bootstrap if the region truly is unusable.
9
+ */
10
+ export type RegionAvailability = "enabled" | "disabled" | "enabling" | "unknown";
11
+ /**
12
+ * Resolve whether `region` is usable for a deploy in this account.
13
+ *
14
+ * Non-opt-in regions are enabled by default and short-circuit to "enabled"
15
+ * with NO API call. Opt-in regions are probed via the Account API
16
+ * (`GetRegionOptStatus`) against an always-on endpoint, with the per-call
17
+ * timeout composed against any caller shutdown signal. Any failure maps to
18
+ * "unknown" (fail-open).
19
+ */
20
+ export declare function checkRegionEnabled(region: string, credentials: AwsProviderCredentials | undefined, abortSignal?: AbortSignal): Promise<RegionAvailability>;
21
+ /**
22
+ * Canonical operator-facing message for a confirmed-disabled opt-in region.
23
+ * Shared by the deploy-entry hard block and the CLI connect pre-flight so the
24
+ * wording cannot drift (reuses `optInRegionWarning` from `@fjall/util`).
25
+ */
26
+ export declare function regionDisabledMessage(region: string): string;
@@ -0,0 +1 @@
1
+ import{AccountClient as a,GetRegionOptStatusCommand as o}from"@aws-sdk/client-account";import{OPT_IN_REGION_CODES as r,optInRegionWarning as i}from"@fjall/util";import{composeSdkAbortSignal as s}from"../organisations/types.js";const c=1e4,l="us-east-1";async function A(n,e,t){if(!r.has(n))return"enabled";try{switch((await new a({region:l,credentials:e}).send(new o({RegionName:n}),{abortSignal:s(t,c)})).RegionOptStatus){case"ENABLED":case"ENABLED_BY_DEFAULT":return"enabled";case"DISABLED":case"DISABLING":return"disabled";case"ENABLING":return"enabling";default:return"unknown"}}catch{return"unknown"}}function S(n){return`Region ${n} is not enabled in this AWS account. ${i(n)} Enable it and wait for activation, then retry \u2014 or deploy to a region that is already enabled.`}export{A as checkRegionEnabled,S as regionDisabledMessage};
@@ -1 +1 @@
1
- import{CloudFormationClient as g,DescribeStackEventsCommand as y,DescribeStacksCommand as A}from"@aws-sdk/client-cloudformation";import{logger as m}from"@fjall/util/logger";import{getErrorMessage as S,maskSensitiveOutput as C}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as f}from"@fjall/util/aws";import{CF_STACK_RESOURCE_TYPE as I}from"./cloudformationEventTypes.js";import{extractErrorName as P}from"../organisations/types.js";const L=["CREATE_COMPLETE","CREATE_FAILED","DELETE_COMPLETE","DELETE_FAILED","UPDATE_COMPLETE","UPDATE_FAILED","UPDATE_ROLLBACK_COMPLETE","UPDATE_ROLLBACK_FAILED","ROLLBACK_COMPLETE","ROLLBACK_FAILED","IMPORT_COMPLETE","IMPORT_ROLLBACK_COMPLETE","IMPORT_ROLLBACK_FAILED"],h=["CREATE_COMPLETE","UPDATE_COMPLETE","DELETE_COMPLETE","IMPORT_COMPLETE"];function k(s){return L.includes(s)}function K(s){return h.includes(s)}const O=new Set(["AWS::EC2::IPAMPool","AWS::EC2::IPAMPoolCidr","AWS::EC2::IPAM"]);function M(s){return O.has(s)}class b{inProgress=new Map;get size(){return this.inProgress.size}clear(){this.inProgress.clear()}trackEvent(e,i){if(e.status.includes("IN_PROGRESS")){this.inProgress.set(e.logicalId,{resourceType:e.resourceType,startTime:e.timestamp});const o=Array.from(this.inProgress.entries()).map(([a,r])=>`${a} (${r.resourceType.split("::").pop()}, started ${r.startTime.toISOString()})`);m.debug("CloudFormation","IPAM resource started",{logicalId:e.logicalId,resourceType:e.resourceType,concurrentIpamCount:this.inProgress.size,concurrentIpamResources:o}),i&&i.writeEvent(e,{phase:"ipam_concurrency",isMainStack:!0,parentStack:`concurrent=${this.inProgress.size}: ${o.join(", ")}`})}else if(e.status.includes("COMPLETE")||e.status.includes("FAILED")){const o=this.inProgress.has(e.logicalId),a=this.inProgress.get(e.logicalId);this.inProgress.delete(e.logicalId);const r=a?e.timestamp.getTime()-a.startTime.getTime():void 0;if(m.debug("CloudFormation",`IPAM resource ${e.status}`,{logicalId:e.logicalId,resourceType:e.resourceType,status:e.status,statusReason:e.statusReason,durationMs:r,wasTracked:o,remainingIpamCount:this.inProgress.size,remainingIpamResources:Array.from(this.inProgress.keys())}),e.status.includes("FAILED")&&i){const l=Array.from(this.inProgress.entries()).map(([n,c])=>({logicalId:n,resourceType:c.resourceType,startTime:c.startTime.toISOString(),inFlightDurationMs:e.timestamp.getTime()-c.startTime.getTime()}));i.writeEvent(e,{phase:"ipam_failure_snapshot",isMainStack:!0,parentStack:JSON.stringify({failedResource:e.logicalId,reason:e.statusReason,durationMs:r,concurrentIpamAtFailure:l})})}}}}async function v(s,e,i){try{const o=s.aws.getClient(g);let a,r=!1,l=!1;do{const n=new y({StackName:e,...a?{NextToken:a}:{}}),c=await o.send(n);if(!c.StackEvents)break;for(const t of c.StackEvents){const d=`${t.EventId}`;if(s.seenEventIds.has(d)){l=!0;continue}if(s.seenEventIds.add(d),t.ResourceType===I&&t.LogicalResourceId!==e){const E=t.ResourceStatus||"",T=t.LogicalResourceId||"";E.includes("IN_PROGRESS")?s.activeNestedStacks.set(T,E):(E.includes("COMPLETE")||E.includes("FAILED"))&&s.activeNestedStacks.delete(T)}if(t.LogicalResourceId===e&&t.ResourceType===I){const E=t.ResourceStatus||"";k(E)&&(r=!0)}t.ResourceStatus&&t.ResourceStatus.includes("FAILED")&&t.ResourceStatusReason&&(s.failureReasons.set(t.LogicalResourceId||"unknown",t.ResourceStatusReason),m.debug("CloudFormation",`Captured failure: ${t.LogicalResourceId} - ${t.ResourceStatusReason}`));const u={logicalId:t.LogicalResourceId||"",physicalId:t.PhysicalResourceId,resourceType:t.ResourceType||"",status:t.ResourceStatus||"",statusReason:t.ResourceStatusReason,timestamp:t.Timestamp||new Date},p=u.logicalId,R=s.eventHistory.get(p)??[];if(R.push(u),s.eventHistory.set(p,R),s.eventHistory.size>s.maxHistorySize){const E=[...s.eventHistory.keys()].slice(0,s.eventHistory.size-s.maxHistorySize);for(const T of E)s.eventHistory.delete(T)}M(u.resourceType)&&s.ipamTracker.trackEvent(u,s.eventLogger),s.eventLogger&&s.eventLogger.writeEvent(u),m.debug("CloudFormation","pollEvents delivering event",{stackName:e,logicalId:u.logicalId,status:u.status,resourceType:u.resourceType}),i(u)}a=l?void 0:c.NextToken}while(a);return r&&s.activeNestedStacks.size>0?!1:r}catch(o){const a=P(o),r=S(o);return a==="ValidationError"&&r.includes(f)?!1:a==="Throttling"||a==="TooManyRequestsException"||a==="ThrottlingException"?"throttled":(r.includes(f)||m.error("CloudFormation","Unexpected polling error",{error:C(r)}),!1)}}async function $(s,e,i,o,a,r){const l=await _(s,e),n=l?.status||"UNKNOWN",c=n.endsWith("_COMPLETE")&&!n.includes("ROLLBACK")&&!n.includes("FAILED");let t=l?.statusReason;!c&&i.size>0&&(t=(Array.from(i.values())[0]??t)||t);let d=null;return o&&(o.writeDeploymentEnd(c,n,t),!c&&i.size>0&&o.writeFailureSummary(Array.from(i.entries()).map(([u,p])=>({logicalId:u,reason:p}))),!c&&a&&(d=a.analyseFailure(r),d&&o.writeFailureAnalysis({rootCause:d.rootCause.reason,failedResources:d.affectedResources.map(u=>({logicalId:u.logicalId,resourceType:u.resourceType,reason:u.statusReason||"Unknown reason"})),dependencyChain:d.dependencyChain,remediation:d.remediation}))),{success:c,failureMessage:t,analysis:d}}async function _(s,e){try{const i=new A({StackName:e}),r=(await s.getClient(g).send(i)).Stacks?.[0];return r?{status:r.StackStatus||"UNKNOWN",statusReason:r.StackStatusReason}:null}catch(i){return m.debug("CloudFormation","getStackStatus failed",{error:S(i)}),null}}async function H(s,e){const i=[];try{const o=new y({StackName:e}),r=await s.getClient(g).send(o);if(!r.StackEvents)return i;const l=new Map;for(const n of r.StackEvents){const c=n.LogicalResourceId||"";if(c===e&&n.ResourceType===I)continue;const t=l.get(c);(!t||n.Timestamp&&t.timestamp<n.Timestamp)&&l.set(c,{logicalId:c,physicalId:n.PhysicalResourceId,resourceType:n.ResourceType||"",status:n.ResourceStatus||"",statusReason:n.ResourceStatusReason,timestamp:n.Timestamp||new Date})}return Array.from(l.values())}catch(o){const a=P(o),r=S(o);return a==="ValidationError"&&r.includes(f)||r.includes(f)||m.error("CloudFormation","Error getting current resources",{error:C(S(o))}),i}}export{b as IpamConcurrencyTracker,H as fetchCurrentResources,_ as fetchStackStatus,$ as handleStackCompletion,M as isIpamResource,K as isSuccessState,k as isTerminalState,v as pollStackEvents};
1
+ import{CloudFormationClient as g,DescribeStackEventsCommand as y,DescribeStacksCommand as A}from"@aws-sdk/client-cloudformation";import{logger as m}from"@fjall/util/logger";import{getErrorMessage as S,maskSensitiveOutput as C}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as f,maskFailureAnalysis as L}from"@fjall/util/aws";import{CF_STACK_RESOURCE_TYPE as I}from"./cloudformationEventTypes.js";import{extractErrorName as P}from"../organisations/types.js";const h=["CREATE_COMPLETE","CREATE_FAILED","DELETE_COMPLETE","DELETE_FAILED","UPDATE_COMPLETE","UPDATE_FAILED","UPDATE_ROLLBACK_COMPLETE","UPDATE_ROLLBACK_FAILED","ROLLBACK_COMPLETE","ROLLBACK_FAILED","IMPORT_COMPLETE","IMPORT_ROLLBACK_COMPLETE","IMPORT_ROLLBACK_FAILED"],k=["CREATE_COMPLETE","UPDATE_COMPLETE","DELETE_COMPLETE","IMPORT_COMPLETE"];function O(s){return h.includes(s)}function b(s){return k.includes(s)}const M=new Set(["AWS::EC2::IPAMPool","AWS::EC2::IPAMPoolCidr","AWS::EC2::IPAM"]);function _(s){return M.has(s)}class v{inProgress=new Map;get size(){return this.inProgress.size}clear(){this.inProgress.clear()}trackEvent(e,i){if(e.status.includes("IN_PROGRESS")){this.inProgress.set(e.logicalId,{resourceType:e.resourceType,startTime:e.timestamp});const o=Array.from(this.inProgress.entries()).map(([a,r])=>`${a} (${r.resourceType.split("::").pop()}, started ${r.startTime.toISOString()})`);m.debug("CloudFormation","IPAM resource started",{logicalId:e.logicalId,resourceType:e.resourceType,concurrentIpamCount:this.inProgress.size,concurrentIpamResources:o}),i&&i.writeEvent(e,{phase:"ipam_concurrency",isMainStack:!0,parentStack:`concurrent=${this.inProgress.size}: ${o.join(", ")}`})}else if(e.status.includes("COMPLETE")||e.status.includes("FAILED")){const o=this.inProgress.has(e.logicalId),a=this.inProgress.get(e.logicalId);this.inProgress.delete(e.logicalId);const r=a?e.timestamp.getTime()-a.startTime.getTime():void 0;if(m.debug("CloudFormation",`IPAM resource ${e.status}`,{logicalId:e.logicalId,resourceType:e.resourceType,status:e.status,statusReason:e.statusReason,durationMs:r,wasTracked:o,remainingIpamCount:this.inProgress.size,remainingIpamResources:Array.from(this.inProgress.keys())}),e.status.includes("FAILED")&&i){const l=Array.from(this.inProgress.entries()).map(([c,u])=>({logicalId:c,resourceType:u.resourceType,startTime:u.startTime.toISOString(),inFlightDurationMs:e.timestamp.getTime()-u.startTime.getTime()}));i.writeEvent(e,{phase:"ipam_failure_snapshot",isMainStack:!0,parentStack:JSON.stringify({failedResource:e.logicalId,reason:e.statusReason,durationMs:r,concurrentIpamAtFailure:l})})}}}}async function $(s,e,i){try{const o=s.aws.getClient(g);let a,r=!1,l=!1;do{const c=new y({StackName:e,...a?{NextToken:a}:{}}),u=await o.send(c);if(!u.StackEvents)break;for(const t of u.StackEvents){const d=`${t.EventId}`;if(s.seenEventIds.has(d)){l=!0;continue}if(s.seenEventIds.add(d),t.ResourceType===I&&t.LogicalResourceId!==e){const E=t.ResourceStatus||"",T=t.LogicalResourceId||"";E.includes("IN_PROGRESS")?s.activeNestedStacks.set(T,E):(E.includes("COMPLETE")||E.includes("FAILED"))&&s.activeNestedStacks.delete(T)}if(t.LogicalResourceId===e&&t.ResourceType===I){const E=t.ResourceStatus||"";O(E)&&(r=!0)}t.ResourceStatus&&t.ResourceStatus.includes("FAILED")&&t.ResourceStatusReason&&(s.failureReasons.set(t.LogicalResourceId||"unknown",t.ResourceStatusReason),m.debug("CloudFormation",`Captured failure: ${t.LogicalResourceId} - ${t.ResourceStatusReason}`));const n={logicalId:t.LogicalResourceId||"",physicalId:t.PhysicalResourceId,resourceType:t.ResourceType||"",status:t.ResourceStatus||"",statusReason:t.ResourceStatusReason,timestamp:t.Timestamp||new Date},p=n.logicalId,R=s.eventHistory.get(p)??[];if(R.push(n),s.eventHistory.set(p,R),s.eventHistory.size>s.maxHistorySize){const E=[...s.eventHistory.keys()].slice(0,s.eventHistory.size-s.maxHistorySize);for(const T of E)s.eventHistory.delete(T)}_(n.resourceType)&&s.ipamTracker.trackEvent(n,s.eventLogger),s.eventLogger&&s.eventLogger.writeEvent(n),m.debug("CloudFormation","pollEvents delivering event",{stackName:e,logicalId:n.logicalId,status:n.status,resourceType:n.resourceType}),i(n)}a=l?void 0:u.NextToken}while(a);return r&&s.activeNestedStacks.size>0?!1:r}catch(o){const a=P(o),r=S(o);return a==="ValidationError"&&r.includes(f)?!1:a==="Throttling"||a==="TooManyRequestsException"||a==="ThrottlingException"?"throttled":(r.includes(f)||m.error("CloudFormation","Unexpected polling error",{error:C(r)}),!1)}}async function H(s,e,i,o,a,r){const l=await F(s,e),c=l?.status||"UNKNOWN",u=c.endsWith("_COMPLETE")&&!c.includes("ROLLBACK")&&!c.includes("FAILED");let t=l?.statusReason;!u&&i.size>0&&(t=(Array.from(i.values())[0]??t)||t);let d=null;if(!u&&a){const n=a.analyseFailure(r);d=n===null?null:L(n)}return o&&(o.writeDeploymentEnd(u,c,t),!u&&i.size>0&&o.writeFailureSummary(Array.from(i.entries()).map(([n,p])=>({logicalId:n,reason:p}))),d&&o.writeFailureAnalysis({rootCause:d.rootCause.reason,failedResources:d.affectedResources.map(n=>({logicalId:n.logicalId,resourceType:n.resourceType,reason:n.statusReason||"Unknown reason"})),dependencyChain:d.dependencyChain,remediation:d.remediation})),{success:u,failureMessage:t,analysis:d}}async function F(s,e){try{const i=new A({StackName:e}),r=(await s.getClient(g).send(i)).Stacks?.[0];return r?{status:r.StackStatus||"UNKNOWN",statusReason:r.StackStatusReason}:null}catch(i){return m.debug("CloudFormation","getStackStatus failed",{error:S(i)}),null}}async function B(s,e){const i=[];try{const o=new y({StackName:e}),r=await s.getClient(g).send(o);if(!r.StackEvents)return i;const l=new Map;for(const c of r.StackEvents){const u=c.LogicalResourceId||"";if(u===e&&c.ResourceType===I)continue;const t=l.get(u);(!t||c.Timestamp&&t.timestamp<c.Timestamp)&&l.set(u,{logicalId:u,physicalId:c.PhysicalResourceId,resourceType:c.ResourceType||"",status:c.ResourceStatus||"",statusReason:c.ResourceStatusReason,timestamp:c.Timestamp||new Date})}return Array.from(l.values())}catch(o){const a=P(o),r=S(o);return a==="ValidationError"&&r.includes(f)||r.includes(f)||m.error("CloudFormation","Error getting current resources",{error:C(S(o))}),i}}export{v as IpamConcurrencyTracker,B as fetchCurrentResources,F as fetchStackStatus,H as handleStackCompletion,_ as isIpamResource,b as isSuccessState,O as isTerminalState,$ as pollStackEvents};
@@ -40,6 +40,12 @@ export type EventLogWriterFactory = (deploymentId: string, stackName: string, re
40
40
  export interface EventMonitorDeps {
41
41
  failureAnalyser?: EventFailureAnalyser;
42
42
  eventLogWriterFactory?: EventLogWriterFactory;
43
+ /**
44
+ * Fired once when a stack reaches a failed terminal state, carrying the
45
+ * computed analysis. Baked into the monitor at construction so it emits from
46
+ * `handleStackComplete` regardless of whether event-logging is enabled.
47
+ */
48
+ onFailureAnalysis?: (analysis: FailureAnalysis) => void;
43
49
  }
44
50
  /** CloudFormation resource type for nested stacks */
45
51
  export declare const CF_STACK_RESOURCE_TYPE = "AWS::CloudFormation::Stack";
@@ -13,6 +13,7 @@ export declare class CloudFormationEventMonitor {
13
13
  private eventLogger;
14
14
  private failureAnalyser;
15
15
  private eventLogWriterFactory;
16
+ private onFailureAnalysis;
16
17
  private lastAnalysis;
17
18
  private deploymentStartTime;
18
19
  private maxHistorySize;
@@ -1 +1 @@
1
- import{logger as u}from"@fjall/util/logger";import{getErrorMessage as p,sleep as d}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as y}from"@fjall/util/aws";import{isResourceEvent as x,STACK_NOT_FOUND_PATTERN as b,CDK_NO_STACKS_MATCH as K}from"@fjall/util/aws";import{CF_STACK_RESOURCE_TYPE as S}from"./cloudformationEventTypes.js";import{isTerminalState as v,isSuccessState as w,IpamConcurrencyTracker as C,pollStackEvents as E,handleStackCompletion as M,fetchStackStatus as T,fetchCurrentResources as L}from"./cloudformationEventHelpers.js";class D{aws;seenEventIds=new Set;isMonitoring=!1;pollInterval=null;activeNestedStacks=new Map;failureReasons=new Map;eventHistory=new Map;eventLogger=null;failureAnalyser;eventLogWriterFactory;lastAnalysis=null;deploymentStartTime=null;maxHistorySize=1e3;maxSeenEventIds=1e4;ipamTracker=new C;constructor(t,e){this.aws=t,this.failureAnalyser=e?.failureAnalyser??null,this.eventLogWriterFactory=e?.eventLogWriterFactory}enableLogging(t,e,o,s){this.eventLogWriterFactory&&(this.eventLogger=this.eventLogWriterFactory(t,e,o,s))}async startMonitoring(t,e,o){if(this.isMonitoring){u.debug("CloudFormation","startMonitoring SKIPPED - already monitoring",{stackName:t});return}u.debug("CloudFormation","startMonitoring STARTED",{stackName:t}),this.isMonitoring=!0,this.seenEventIds.clear(),this.eventHistory.clear(),this.deploymentStartTime=new Date;try{await this.pollEvents(t,()=>{})}catch(n){const c=p(n);c.includes(y)||u.debug("CloudFormation","Initial poll failed",{error:c})}let s=5e3;const a=1e4;let r=0,g=0;const l=async()=>{if(this.isMonitoring)try{const n=await this.pollEvents(t,e);r++,n==="throttled"?(g++,s=Math.min(3e4,5e3*Math.pow(2,g-1))):(g=0,r>20&&s<a?s=Math.min(a,s+1e3):r<=20&&(s=5e3)),n===!0?await this.handleStackComplete(t,o):this.pollInterval=setTimeout(l,s)}catch(n){u.debug("CloudFormation","Polling iteration error (continuing)",{error:p(n)}),this.pollInterval=setTimeout(l,s)}};this.pollInterval=setTimeout(l,s)}stopMonitoring(){u.debug("CloudFormation","stopMonitoring called",{wasMonitoring:this.isMonitoring,seenEventCount:this.seenEventIds.size}),this.isMonitoring=!1,this.pollInterval&&(clearTimeout(this.pollInterval),this.pollInterval=null),this.cleanup()}cleanup(){if(this.activeNestedStacks.clear(),this.failureReasons.clear(),this.eventHistory.clear(),this.ipamTracker.clear(),this.seenEventIds.size>this.maxSeenEventIds){const t=Array.from(this.seenEventIds),e=Math.floor(this.maxSeenEventIds/2);this.seenEventIds=new Set(t.slice(-e))}this.eventLogger&&(this.eventLogger=null)}async handleStackComplete(t,e){const o=new Map(this.failureReasons),s=new Map(this.eventHistory),a=this.eventLogger;this.stopMonitoring();const r=await M(this.aws,t,o,a,this.failureAnalyser,s);this.lastAnalysis=r.analysis,e&&e(r.success,r.failureMessage)}getResourceHistory(t){return this.eventHistory.get(t)||[]}getEventHistory(){return new Map(this.eventHistory)}getFailureAnalysis(){return this.lastAnalysis}getFirstFailureReason(){return this.failureReasons.size>0?Array.from(this.failureReasons.values())[0]??null:null}getEventLogger(){return this.eventLogger}getEventLogPath(){return this.eventLogger?.getLogPath()||null}getLogSummary(){return this.eventLogger?.getLogSummary()||null}async waitForStackComplete(t,e={}){const{timeout:o=1800*1e3,pollInterval:s=2e3,onResourceUpdate:a,onStackComplete:r}=e,g=Date.now();let l,n=!1,c=!1,h;u.debug("CloudFormation","waitForStackComplete called",{stackName:t,timeout:o,pollInterval:s,hasOnResourceUpdate:!!a}),await this.startMonitoring(t,i=>{a&&a(i),i.resourceType===S&&i.logicalId===t&&(l=i.status,v(i.status)&&(n=!0,c=w(i.status),c||(h=this.getFirstFailureReason()||i.statusReason||"Stack operation failed")))},(i,f)=>{r&&r(i,f)});try{let i=!1,f=!1;for(;!n&&this.isMonitoring&&Date.now()-g<o;)await d(s),!i&&Date.now()-g>3e4&&!f&&(f=!0,await this.getStackStatus(t)||u.debug("CloudFormation","Stack not found after 30s, continuing to wait (CDK may be uploading assets)",{stackName:t})),l&&(i=!0);if(this.stopMonitoring(),!n){if(h)return{success:!1,status:"FAILED",failureReason:h,logPath:this.getLogSummary()||void 0};const m=await this.getStackStatus(t);return{success:!1,status:m?.status||"UNKNOWN",failureReason:`Deployment timed out after ${o/1e3} seconds. Stack status: ${m?.status||"UNKNOWN"}`,logPath:this.getLogSummary()||void 0}}return{success:c,status:l,failureReason:h,logPath:this.getLogSummary()||void 0}}catch(i){return this.stopMonitoring(),{success:!1,failureReason:`Monitoring error: ${p(i)}`,logPath:this.getLogSummary()||void 0}}}pollContext(){return{aws:this.aws,seenEventIds:this.seenEventIds,activeNestedStacks:this.activeNestedStacks,failureReasons:this.failureReasons,eventHistory:this.eventHistory,maxHistorySize:this.maxHistorySize,ipamTracker:this.ipamTracker,eventLogger:this.eventLogger}}async pollEvents(t,e){return E(this.pollContext(),t,e)}async getStackStatus(t){return T(this.aws,t)}async getCurrentResources(t){return L(this.aws,t)}}export{K as CDK_NO_STACKS_MATCH,D as CloudFormationEventMonitor,b as STACK_NOT_FOUND_PATTERN,x as isResourceEvent};
1
+ import{logger as u}from"@fjall/util/logger";import{getErrorMessage as m,sleep as p}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as d}from"@fjall/util/aws";import{isResourceEvent as x,STACK_NOT_FOUND_PATTERN as b,CDK_NO_STACKS_MATCH as K}from"@fjall/util/aws";import{CF_STACK_RESOURCE_TYPE as S}from"./cloudformationEventTypes.js";import{isTerminalState as v,isSuccessState as w,IpamConcurrencyTracker as C,pollStackEvents as E,handleStackCompletion as M,fetchStackStatus as F,fetchCurrentResources as T}from"./cloudformationEventHelpers.js";class D{aws;seenEventIds=new Set;isMonitoring=!1;pollInterval=null;activeNestedStacks=new Map;failureReasons=new Map;eventHistory=new Map;eventLogger=null;failureAnalyser;eventLogWriterFactory;onFailureAnalysis;lastAnalysis=null;deploymentStartTime=null;maxHistorySize=1e3;maxSeenEventIds=1e4;ipamTracker=new C;constructor(t,e){this.aws=t,this.failureAnalyser=e?.failureAnalyser??null,this.eventLogWriterFactory=e?.eventLogWriterFactory,this.onFailureAnalysis=e?.onFailureAnalysis}enableLogging(t,e,a,s){this.eventLogWriterFactory&&(this.eventLogger=this.eventLogWriterFactory(t,e,a,s))}async startMonitoring(t,e,a){if(this.isMonitoring){u.debug("CloudFormation","startMonitoring SKIPPED - already monitoring",{stackName:t});return}u.debug("CloudFormation","startMonitoring STARTED",{stackName:t}),this.isMonitoring=!0,this.seenEventIds.clear(),this.eventHistory.clear(),this.deploymentStartTime=new Date;try{await this.pollEvents(t,()=>{})}catch(r){const h=m(r);h.includes(d)||u.debug("CloudFormation","Initial poll failed",{error:h})}let s=5e3;const o=1e4;let n=0,g=0;const l=async()=>{if(this.isMonitoring)try{const r=await this.pollEvents(t,e);n++,r==="throttled"?(g++,s=Math.min(3e4,5e3*Math.pow(2,g-1))):(g=0,n>20&&s<o?s=Math.min(o,s+1e3):n<=20&&(s=5e3)),r===!0?await this.handleStackComplete(t,a):this.pollInterval=setTimeout(l,s)}catch(r){u.debug("CloudFormation","Polling iteration error (continuing)",{error:m(r)}),this.pollInterval=setTimeout(l,s)}};this.pollInterval=setTimeout(l,s)}stopMonitoring(){u.debug("CloudFormation","stopMonitoring called",{wasMonitoring:this.isMonitoring,seenEventCount:this.seenEventIds.size}),this.isMonitoring=!1,this.pollInterval&&(clearTimeout(this.pollInterval),this.pollInterval=null),this.cleanup()}cleanup(){if(this.activeNestedStacks.clear(),this.failureReasons.clear(),this.eventHistory.clear(),this.ipamTracker.clear(),this.seenEventIds.size>this.maxSeenEventIds){const t=Array.from(this.seenEventIds),e=Math.floor(this.maxSeenEventIds/2);this.seenEventIds=new Set(t.slice(-e))}this.eventLogger&&(this.eventLogger=null)}async handleStackComplete(t,e){const a=new Map(this.failureReasons),s=new Map(this.eventHistory),o=this.eventLogger;this.stopMonitoring();const n=await M(this.aws,t,a,o,this.failureAnalyser,s);this.lastAnalysis=n.analysis,n.analysis&&this.onFailureAnalysis&&this.onFailureAnalysis(n.analysis),e&&e(n.success,n.failureMessage)}getResourceHistory(t){return this.eventHistory.get(t)||[]}getEventHistory(){return new Map(this.eventHistory)}getFailureAnalysis(){return this.lastAnalysis}getFirstFailureReason(){return this.failureReasons.size>0?Array.from(this.failureReasons.values())[0]??null:null}getEventLogger(){return this.eventLogger}getEventLogPath(){return this.eventLogger?.getLogPath()||null}getLogSummary(){return this.eventLogger?.getLogSummary()||null}async waitForStackComplete(t,e={}){const{timeout:a=1800*1e3,pollInterval:s=2e3,onResourceUpdate:o,onStackComplete:n}=e,g=Date.now();let l,r=!1,h=!1,c;u.debug("CloudFormation","waitForStackComplete called",{stackName:t,timeout:a,pollInterval:s,hasOnResourceUpdate:!!o}),await this.startMonitoring(t,i=>{o&&o(i),i.resourceType===S&&i.logicalId===t&&(l=i.status,v(i.status)&&(r=!0,h=w(i.status),h||(c=this.getFirstFailureReason()||i.statusReason||"Stack operation failed")))},(i,f)=>{n&&n(i,f)});try{let i=!1,f=!1;for(;!r&&this.isMonitoring&&Date.now()-g<a;)await p(s),!i&&Date.now()-g>3e4&&!f&&(f=!0,await this.getStackStatus(t)||u.debug("CloudFormation","Stack not found after 30s, continuing to wait (CDK may be uploading assets)",{stackName:t})),l&&(i=!0);if(this.stopMonitoring(),!r){if(c)return{success:!1,status:"FAILED",failureReason:c,logPath:this.getLogSummary()||void 0};const y=await this.getStackStatus(t);return{success:!1,status:y?.status||"UNKNOWN",failureReason:`Deployment timed out after ${a/1e3} seconds. Stack status: ${y?.status||"UNKNOWN"}`,logPath:this.getLogSummary()||void 0}}return{success:h,status:l,failureReason:c,logPath:this.getLogSummary()||void 0}}catch(i){return this.stopMonitoring(),{success:!1,failureReason:`Monitoring error: ${m(i)}`,logPath:this.getLogSummary()||void 0}}}pollContext(){return{aws:this.aws,seenEventIds:this.seenEventIds,activeNestedStacks:this.activeNestedStacks,failureReasons:this.failureReasons,eventHistory:this.eventHistory,maxHistorySize:this.maxHistorySize,ipamTracker:this.ipamTracker,eventLogger:this.eventLogger}}async pollEvents(t,e){return E(this.pollContext(),t,e)}async getStackStatus(t){return F(this.aws,t)}async getCurrentResources(t){return T(this.aws,t)}}export{K as CDK_NO_STACKS_MATCH,D as CloudFormationEventMonitor,b as STACK_NOT_FOUND_PATTERN,x as isResourceEvent};
@@ -13,11 +13,15 @@
13
13
  */
14
14
  export { DeploymentEventSchema, DEPLOYMENT_EVENT_TYPES, DEPLOYMENT_EVENT_RESOURCE_CATEGORIES, DEPLOYMENT_EVENT_STATUS_REASON_MAX, DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX, DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX, CASCADE_PHASES, CASCADE_ACCOUNT_STATUSES } from "./types/index.js";
15
15
  export type { DeploymentEvent, DeploymentEventType, DeploymentEventResourceCategory, DeploymentEventCascadePhase, DeploymentEventCascadeAccountStatus } from "./types/index.js";
16
- export type { AwsCredentials, DeployIdentity, DeployCallbacks, StepCompleteStatus, StackCleanupQuarantineDetail, StackCleanupRetainedBucketsDetail, ProgressEvent, ProgressEventType, ResourceEvent, AwsAuthResult, CascadeDeploymentResult, CascadePhase, BuildPushStartEvent, BuildPushProgressEvent, BuildPushCompleteEvent, TaskDefRegisteredEvent, ECSCompleteEvent, MigrationsStartEvent, MigrationsCompleteEvent, TrailMigrationPhase, TrailMigrationStatus, TrailMigrationPhaseEvent, ApiClientInterface, EntitlementsData, DeployParams, DeployOptions, DeploymentType, DeployResult, DestroyParams, DestroyOptions, DestroyResult, OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession, Entitlements } from "./types/index.js";
16
+ export { DeployEventSchema, StepProgressSchema, DeployResourceSchema, PlanChangeSchema, PlanPropertyChangeSchema, ChangeCountsSchema, DEPLOY_EVENT_CONTRACT, DEPLOY_EVENT_CONTRACT_MAJOR, DEPLOY_EVENT_CONTRACT_MINOR, SUPPORTED_CONTRACT_MARKERS, DEPLOY_EVENT_ID_MAX, DEPLOY_EVENT_NAME_MAX, DEPLOY_EVENT_MESSAGE_MAX, DEPLOY_EVENT_STATUS_REASON_MAX, DEPLOY_EVENT_ERROR_MESSAGE_MAX, DEPLOY_EVENT_TRAIL_DETAIL_MAX, DEPLOY_EVENT_OUTPUT_MAX, DEPLOY_EVENT_ARN_MAX, DEPLOY_EVENT_URL_MAX, STEP_COMPLETE_STATUSES, LOG_LEVELS, STACK_CLEANUP_PHASES, CASCADE_ACCOUNT_PHASES, DEPLOY_RESULTS, DETECTION_PHASES, PHASE_STATUSES, BUILDERS, CASCADE_OUTCOME_RESULTS, PLAN_ACTIONS, REPLACEMENT_MODES, APPROVAL_KINDS, APPROVAL_DECISIONS, ALL_PROGRESS_KINDS, createDeployEmitter, parseDeployEvent, parseDeployContractVersion } from "./types/index.js";
17
+ export type { DeployEvent, ProgressKind, StepProgress, DeployResource, PlanChange, PlanPropertyChange, ChangeCounts, PlanAction, ReplacementMode, ApprovalKind, ApprovalDecision, DeployRunResult, DeployEmitter, CreateDeployEmitterOptions, ParsedDeployEvent, StackCleanupPhase, CascadeAccountPhase, LogLevel } from "./types/index.js";
18
+ export type { AwsCredentials, DeployIdentity, DeployCallbacks, StepCompleteStatus, StackCleanupQuarantineDetail, StackCleanupRetainedBucketsDetail, ProgressEvent, ProgressEventType, ResourceEvent, AwsAuthResult, CascadeDeploymentResult, CascadePhase, BuildPushStartEvent, BuildPushProgressEvent, BuildPushCompleteEvent, TaskDefRegisteredEvent, ECSCompleteEvent, MigrationsStartEvent, MigrationsCompleteEvent, TrailMigrationPhase, TrailMigrationStatus, TrailMigrationPhaseEvent, ApiClientInterface, EntitlementsData, DeployParams, DeployOptions, DeploymentType, DeployResult, DestroyParams, DestroyOptions, DestroyResult, ApprovalGate, ApprovalRequest, GateResolution, OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession, Entitlements } from "./types/index.js";
17
19
  export type { AwsProvider, AwsProviderCredentials, AwsSdkClientConstructor } from "./aws/index.js";
18
20
  export { SimpleAwsProvider } from "./aws/index.js";
19
21
  export { checkTargetReadiness, describeTargetReadinessReason, buildTargetUnreadyAdvisory, buildTargetSetUnreadyAdvisory, probeAccountStackExists } from "./aws/index.js";
20
22
  export type { TargetReadinessAccount, TargetReadinessClients, TargetReadinessReason, TargetReadinessVerdict, TargetSetUnreadyAdvisoryParams } from "./aws/index.js";
23
+ export { checkRegionEnabled, regionDisabledMessage } from "./aws/index.js";
24
+ export type { RegionAvailability } from "./aws/index.js";
21
25
  export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, extractErrorName, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./aws/index.js";
22
26
  export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./aws/index.js";
23
27
  export { STEP_IDS, STEP_NAMES, INFRASTRUCTURE_STEP_NAMES, INFRA_STEP_NAME } from "./types/index.js";
@@ -59,8 +63,12 @@ export { parseAccountsConfiguration, flattenAccountsToEnvironments, extractAllAc
59
63
  export type { AccountsConfig } from "./orchestration/index.js";
60
64
  export { runOpenNextBuild } from "./orchestration/index.js";
61
65
  export { runOrganisationSetup, ORG_SETUP_PHASES } from "./orchestration/index.js";
62
- export type { OrgSetupPhase, OrgSetupCallbacks, OrgSetupConfig, OrgSetupResult, DockerProvider, DockerProgressCallback, DockerBuildParams, DockerBuildResult, ECRInitParams, ECRInitResult, TagImagesParams, TagImagesResult, TagByDigestParams, DomainDeployProvider, DomainConfig, DomainDeployResult, DeployServices } from "./orchestration/index.js";
66
+ export type { OrgSetupPhase, OrgSetupCallbacks, OrgSetupConfig, OrgSetupResult, DockerProvider, DockerProgressCallback, DockerBuildParams, DockerBuildResult, ResolvedBuildSecret, ResolvedBuildSecrets, ECRInitParams, ECRInitResult, TagImagesParams, TagImagesResult, TagByDigestParams, DomainDeployProvider, DomainConfig, DomainDeployResult, DeployServices } from "./orchestration/index.js";
67
+ export { resolveBuildSecrets, resolveSecretRefValue, resolveBuildSecretSessionProvider, sourcedRefsFromBuildArgs, resolveBuildArgs, buildBuildSecretSessionPolicy, partitionForRegion, validateBuildGroup } from "./orchestration/index.js";
68
+ export type { ResolveBuildArgsContext, ResolveBuildArgsResult, SourcedBuildArgResolver, BuildSecretRef, BuildSecretSessionContext, ValidateBuildGroupInput, BuildGroupMemberInputs } from "./orchestration/index.js";
63
69
  export type { FrameworkBuilder, FrameworkDetection, BuildPlan, BuildCommand, BuildCallbacks, DetectionContext, BuildOptions } from "./types/index.js";
64
70
  export { FrameworkRegistry, type ResolvedBuilder } from "./orchestration/index.js";
65
71
  export { openNextBuilder, dockerBuilder } from "./orchestration/index.js";
66
72
  export { StepRegistry, getDestroyStepId } from "./steps/index.js";
73
+ export { buildDeployPlan, computeDeployPlan, computeAssemblyDigest, digestHead, classifyImpact, derivePropertyChanges, isDataLoss, isStatefulResourceType, renderPlanSummary, renderPlanLines, toWirePlanChanges, signApprovalToken, verifyApprovalToken, DEFAULT_APPROVAL_TTL_MS, APPROVAL_TOKEN_PATTERN, runApprovalGate } from "./orchestration/index.js";
74
+ export type { DeployPlan, DeployPlanResourceChange, StackTemplatePair, CfnTemplateReader, ComputeDeployPlanParams, ComputeDeployPlanStack, ImpactClassification, SignApprovalTokenOptions, SignedApprovalToken, VerifyApprovalTokenOptions, VerifyApprovalTokenResult, RunApprovalGateParams, ApprovalGateOutcome } from "./orchestration/index.js";
package/dist/src/index.js CHANGED
@@ -1 +1 @@
1
- import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as i,DEPLOYMENT_EVENT_STATUS_REASON_MAX as a,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as s,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as c,CASCADE_PHASES as n,CASCADE_ACCOUNT_STATUSES as E}from"./types/index.js";import{SimpleAwsProvider as l}from"./aws/index.js";import{checkTargetReadiness as T,describeTargetReadinessReason as p,buildTargetUnreadyAdvisory as u,buildTargetSetUnreadyAdvisory as _,probeAccountStackExists as O}from"./aws/index.js";import{ensureOrganisationExists as m,describeOrganisation as d,enablePolicyTypes as P,enableServiceAccess as C,SERVICE_PRINCIPALS as N,enableRamSharing as f,activateTrustedAccess as g,enableIpamDelegatedAdmin as x,updateBackupGlobalSettings as D,listAccounts as I,findAccount as L,createAccount as U,ensureOrganisationalUnitsExist as k,placeAccountsInOUs as y,buildAccountToOUMap as v,activateCostAllocationTags as M,checkIdentityCentreStatus as F,extractErrorName as Y,isOULeaf as b,registerSecurityDelegates as B,SECURITY_SERVICE_PRINCIPALS as h,enableCentralisedRootAccess as K,ROOT_ACCESS_FEATURES as G,RootAccessEnablementError as V}from"./aws/index.js";import{STEP_IDS as X,STEP_NAMES as w,INFRASTRUCTURE_STEP_NAMES as j,INFRA_STEP_NAME as Q}from"./types/index.js";import{ProgressReporter as z,APPLICATION_STACKS as J,ORGANISATION_TYPES as W,APPLICATION_DEPLOY_ORDER as Z,APPLICATION_DESTROY_ORDER as $,OPENNEXT_DEPLOY_ORDER as ee,OPENNEXT_DESTROY_ORDER as re,PARALLEL_DEPLOY_GROUPS as te,PARALLEL_DESTROY_GROUPS as oe,OPENNEXT_PARALLEL_GROUPS as ie,PARALLEL_OPERATION_TYPES as ae,isApplicationOperation as se,isOrganisationOperation as ce,getParallelDeployGroups as ne,getParallelDestroyGroups as Ee,getApplicationDeployOrder as Se,getApplicationDestroyOrder as le,getApplicationStackName as Ae,getOrganisationStackName as Te,isApplicationStack as pe,isQuarantineDetail as ue,isRetainedBucketsDetail as _e,getApplicationStepName as Oe,getApplicationStepId as Re,toPascalCase as me,isOpenNextPattern as de,OPENNEXT_PATTERNS as Pe,deriveResourcesFromManifestStacks as Ce,STACK_NOT_FOUND_PATTERN as Ne,STACK_FAILED_STATE_PATTERN as fe,CDK_NO_STACKS_MATCH as ge,INFRASTRUCTURE_FILENAME as xe,ApplicationError as De,wrapApplicationError as Ie,FjallStateFileSchema as Le,readStateFile as Ue,writeStateFile as ke,createEmptyState as ye,deleteStateFile as ve,updateTemplateHash as Me,getStateFilePath as Fe,stubCallerIdentity as Ye}from"./types/index.js";import{CloudFormationEventMonitor as Be}from"./aws/index.js";import{CdkService as Ke,CdkArgumentBuilder as Ge,CdkProcessManager as Ve,CdkEventMonitor as He,startStackMonitoring as Xe,DEFAULT_DEPLOY_TIMEOUT_MS as we,isCdkError as je,formatInfrastructureError as Qe,getStructuralHint as qe,getSourceContext as ze,hasCdkDifferences as Je,parseDiffOutput as We,CloudFormationService as Ze,CloudFormationError as $e,EcsService as er,EcsError as rr,EcsServiceResolver as tr,TemplateHashService as or,TemplateHashError as ir,CdkContextBuilder as ar,emitProgress as sr,PROGRESS_MESSAGES as cr,parseBuildPhase as nr,buildStepContextBuildConfig as Er,convertCloudFormationOutputsToRecord as Sr,ApplicationStackService as lr}from"./services/index.js";import{CdkError as Tr}from"./types/errors/index.js";import{BaseServiceError as ur,ValidationError as _r,AuthError as Or,AwsError as Rr,DeploymentError as mr,NetworkError as dr,FileSystemError as Pr,ConfigError as Cr,toServiceError as Nr}from"./types/errors/index.js";import{filterDangerousEnvVars as gr,maskSensitiveOutput as xr,parseShellArgs as Dr,sleep as Ir}from"@fjall/util";import{hasDockerfile as Ur}from"./util/dockerfileDetection.js";import{createSequencedCallbacks as yr}from"./util/sequencedCallbacks.js";import{fileExists as Mr}from"@fjall/util/fsHelpers";import{success as Yr,failure as br,isSuccess as Br,isFailure as hr}from"@fjall/generator";import{deploy as Gr}from"./orchestration/index.js";import{destroy as Hr}from"./orchestration/index.js";import{partitionAccounts as wr}from"./orchestration/index.js";import{buildRegionList as Qr,buildAccountRegionPairs as qr,cascadeHomeRegion as zr,cascadeOperationKey as Jr}from"./orchestration/index.js";import{projectScalarSummary as Zr,projectAccountRows as $r}from"./orchestration/index.js";import{reconcileProviderAccounts as rt,mergeReconciledProviderAccounts as tt}from"./orchestration/index.js";import{decideNextTransition as it,reconcileTrailMigration as at,decommissionMemberTrailStorage as st,ORG_TRAIL_BUCKET_OUTPUT_KEY as ct,TRAIL_BUCKET_OUTPUT_KEY as nt,TRAIL_KEY_ARN_OUTPUT_KEY as Et}from"./orchestration/index.js";import{unlockBucket as lt,unlockQueue as At}from"./orchestration/index.js";import{triageBucketPolicy as pt,isEnforceSslStatement as ut,toResourcePolicyStatements as _t,restoreBucketPolicy as Ot,synthesiseEnforceSslDocument as Rt,ensureEnforceSsl as mt,restoreAndReconcileQuarantinedBucket as dt}from"./orchestration/index.js";import{verifyOrgTrailDelivery as Ct}from"./aws/cloudtrail/orgTrailDelivery.js";import{assumeRootForTask as ft,isRootTaskPolicyArn as gt,ROOT_TASK_POLICY_ARNS as xt,MAX_ROOT_SESSION_SECONDS as Dt}from"./aws/sts/assumeRoot.js";import{parseAccountsConfiguration as Lt,flattenAccountsToEnvironments as Ut,extractAllAccountNames as kt,accountsConfigToOUTree as yt,isStringArray as vt,isAccountsConfig as Mt,isOuOnlyAccountBucket as Ft,OU_ONLY_ACCOUNT_BUCKETS as Yt}from"./orchestration/index.js";import{runOpenNextBuild as Bt}from"./orchestration/index.js";import{runOrganisationSetup as Kt,ORG_SETUP_PHASES as Gt}from"./orchestration/index.js";import{FrameworkRegistry as Ht}from"./orchestration/index.js";import{openNextBuilder as wt,dockerBuilder as jt}from"./orchestration/index.js";import{StepRegistry as qt,getDestroyStepId as zt}from"./steps/index.js";export{Z as APPLICATION_DEPLOY_ORDER,$ as APPLICATION_DESTROY_ORDER,J as APPLICATION_STACKS,De as ApplicationError,lr as ApplicationStackService,Or as AuthError,Rr as AwsError,ur as BaseServiceError,E as CASCADE_ACCOUNT_STATUSES,n as CASCADE_PHASES,ge as CDK_NO_STACKS_MATCH,Ge as CdkArgumentBuilder,ar as CdkContextBuilder,Tr as CdkError,He as CdkEventMonitor,Ve as CdkProcessManager,Ke as CdkService,$e as CloudFormationError,Be as CloudFormationEventMonitor,Ze as CloudFormationService,Cr as ConfigError,we as DEFAULT_DEPLOY_TIMEOUT_MS,s as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,i as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,a as DEPLOYMENT_EVENT_STATUS_REASON_MAX,c as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,o as DEPLOYMENT_EVENT_TYPES,mr as DeploymentError,t as DeploymentEventSchema,rr as EcsError,er as EcsService,tr as EcsServiceResolver,Pr as FileSystemError,Le as FjallStateFileSchema,Ht as FrameworkRegistry,xe as INFRASTRUCTURE_FILENAME,j as INFRASTRUCTURE_STEP_NAMES,Q as INFRA_STEP_NAME,Dt as MAX_ROOT_SESSION_SECONDS,dr as NetworkError,ee as OPENNEXT_DEPLOY_ORDER,re as OPENNEXT_DESTROY_ORDER,ie as OPENNEXT_PARALLEL_GROUPS,Pe as OPENNEXT_PATTERNS,W as ORGANISATION_TYPES,Gt as ORG_SETUP_PHASES,ct as ORG_TRAIL_BUCKET_OUTPUT_KEY,Yt as OU_ONLY_ACCOUNT_BUCKETS,te as PARALLEL_DEPLOY_GROUPS,oe as PARALLEL_DESTROY_GROUPS,ae as PARALLEL_OPERATION_TYPES,cr as PROGRESS_MESSAGES,z as ProgressReporter,G as ROOT_ACCESS_FEATURES,xt as ROOT_TASK_POLICY_ARNS,V as RootAccessEnablementError,h as SECURITY_SERVICE_PRINCIPALS,N as SERVICE_PRINCIPALS,fe as STACK_FAILED_STATE_PATTERN,Ne as STACK_NOT_FOUND_PATTERN,X as STEP_IDS,w as STEP_NAMES,l as SimpleAwsProvider,qt as StepRegistry,nt as TRAIL_BUCKET_OUTPUT_KEY,Et as TRAIL_KEY_ARN_OUTPUT_KEY,ir as TemplateHashError,or as TemplateHashService,_r as ValidationError,yt as accountsConfigToOUTree,M as activateCostAllocationTags,g as activateTrustedAccess,ft as assumeRootForTask,qr as buildAccountRegionPairs,v as buildAccountToOUMap,Qr as buildRegionList,Er as buildStepContextBuildConfig,_ as buildTargetSetUnreadyAdvisory,u as buildTargetUnreadyAdvisory,zr as cascadeHomeRegion,Jr as cascadeOperationKey,F as checkIdentityCentreStatus,T as checkTargetReadiness,Sr as convertCloudFormationOutputsToRecord,U as createAccount,ye as createEmptyState,yr as createSequencedCallbacks,it as decideNextTransition,st as decommissionMemberTrailStorage,ve as deleteStateFile,Gr as deploy,Ce as deriveResourcesFromManifestStacks,d as describeOrganisation,p as describeTargetReadinessReason,Hr as destroy,jt as dockerBuilder,sr as emitProgress,K as enableCentralisedRootAccess,x as enableIpamDelegatedAdmin,P as enablePolicyTypes,f as enableRamSharing,C as enableServiceAccess,mt as ensureEnforceSsl,m as ensureOrganisationExists,k as ensureOrganisationalUnitsExist,kt as extractAllAccountNames,Y as extractErrorName,br as failure,Mr as fileExists,gr as filterDangerousEnvVars,L as findAccount,Ut as flattenAccountsToEnvironments,Qe as formatInfrastructureError,Se as getApplicationDeployOrder,le as getApplicationDestroyOrder,Ae as getApplicationStackName,Re as getApplicationStepId,Oe as getApplicationStepName,zt as getDestroyStepId,Te as getOrganisationStackName,ne as getParallelDeployGroups,Ee as getParallelDestroyGroups,ze as getSourceContext,Fe as getStateFilePath,qe as getStructuralHint,Je as hasCdkDifferences,Ur as hasDockerfile,Mt as isAccountsConfig,se as isApplicationOperation,pe as isApplicationStack,je as isCdkError,ut as isEnforceSslStatement,hr as isFailure,b as isOULeaf,de as isOpenNextPattern,ce as isOrganisationOperation,Ft as isOuOnlyAccountBucket,ue as isQuarantineDetail,_e as isRetainedBucketsDetail,gt as isRootTaskPolicyArn,vt as isStringArray,Br as isSuccess,I as listAccounts,xr as maskSensitiveOutput,tt as mergeReconciledProviderAccounts,wt as openNextBuilder,Lt as parseAccountsConfiguration,nr as parseBuildPhase,We as parseDiffOutput,Dr as parseShellArgs,wr as partitionAccounts,y as placeAccountsInOUs,O as probeAccountStackExists,$r as projectAccountRows,Zr as projectScalarSummary,Ue as readStateFile,rt as reconcileProviderAccounts,at as reconcileTrailMigration,B as registerSecurityDelegates,dt as restoreAndReconcileQuarantinedBucket,Ot as restoreBucketPolicy,Bt as runOpenNextBuild,Kt as runOrganisationSetup,Ir as sleep,Xe as startStackMonitoring,Ye as stubCallerIdentity,Yr as success,Rt as synthesiseEnforceSslDocument,me as toPascalCase,_t as toResourcePolicyStatements,Nr as toServiceError,pt as triageBucketPolicy,lt as unlockBucket,At as unlockQueue,D as updateBackupGlobalSettings,Me as updateTemplateHash,Ct as verifyOrgTrailDelivery,Ie as wrapApplicationError,ke as writeStateFile};
1
+ import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as E,DEPLOYMENT_EVENT_STATUS_REASON_MAX as a,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as i,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as s,CASCADE_PHASES as S,CASCADE_ACCOUNT_STATUSES as n}from"./types/index.js";import{DeployEventSchema as A,StepProgressSchema as T,DeployResourceSchema as _,PlanChangeSchema as l,PlanPropertyChangeSchema as p,ChangeCountsSchema as O,DEPLOY_EVENT_CONTRACT as P,DEPLOY_EVENT_CONTRACT_MAJOR as R,DEPLOY_EVENT_CONTRACT_MINOR as u,SUPPORTED_CONTRACT_MARKERS as m,DEPLOY_EVENT_ID_MAX as C,DEPLOY_EVENT_NAME_MAX as N,DEPLOY_EVENT_MESSAGE_MAX as d,DEPLOY_EVENT_STATUS_REASON_MAX as D,DEPLOY_EVENT_ERROR_MESSAGE_MAX as L,DEPLOY_EVENT_TRAIL_DETAIL_MAX as g,DEPLOY_EVENT_OUTPUT_MAX as f,DEPLOY_EVENT_ARN_MAX as I,DEPLOY_EVENT_URL_MAX as x,STEP_COMPLETE_STATUSES as U,LOG_LEVELS as y,STACK_CLEANUP_PHASES as M,CASCADE_ACCOUNT_PHASES as v,DEPLOY_RESULTS as k,DETECTION_PHASES as Y,PHASE_STATUSES as V,BUILDERS as h,CASCADE_OUTCOME_RESULTS as F,PLAN_ACTIONS as B,REPLACEMENT_MODES as b,APPROVAL_KINDS as G,APPROVAL_DECISIONS as K,ALL_PROGRESS_KINDS as X,createDeployEmitter as H,parseDeployEvent as w,parseDeployContractVersion as j}from"./types/index.js";import{SimpleAwsProvider as q}from"./aws/index.js";import{checkTargetReadiness as W,describeTargetReadinessReason as z,buildTargetUnreadyAdvisory as Z,buildTargetSetUnreadyAdvisory as $,probeAccountStackExists as ee}from"./aws/index.js";import{checkRegionEnabled as te,regionDisabledMessage as oe}from"./aws/index.js";import{ensureOrganisationExists as ae,describeOrganisation as ie,enablePolicyTypes as se,enableServiceAccess as Se,SERVICE_PRINCIPALS as ne,enableRamSharing as ce,activateTrustedAccess as Ae,enableIpamDelegatedAdmin as Te,updateBackupGlobalSettings as _e,listAccounts as le,findAccount as pe,createAccount as Oe,ensureOrganisationalUnitsExist as Pe,placeAccountsInOUs as Re,buildAccountToOUMap as ue,activateCostAllocationTags as me,checkIdentityCentreStatus as Ce,extractErrorName as Ne,isOULeaf as de,registerSecurityDelegates as De,SECURITY_SERVICE_PRINCIPALS as Le,enableCentralisedRootAccess as ge,ROOT_ACCESS_FEATURES as fe,RootAccessEnablementError as Ie}from"./aws/index.js";import{STEP_IDS as Ue,STEP_NAMES as ye,INFRASTRUCTURE_STEP_NAMES as Me,INFRA_STEP_NAME as ve}from"./types/index.js";import{ProgressReporter as Ye,APPLICATION_STACKS as Ve,ORGANISATION_TYPES as he,APPLICATION_DEPLOY_ORDER as Fe,APPLICATION_DESTROY_ORDER as Be,OPENNEXT_DEPLOY_ORDER as be,OPENNEXT_DESTROY_ORDER as Ge,PARALLEL_DEPLOY_GROUPS as Ke,PARALLEL_DESTROY_GROUPS as Xe,OPENNEXT_PARALLEL_GROUPS as He,PARALLEL_OPERATION_TYPES as we,isApplicationOperation as je,isOrganisationOperation as Qe,getParallelDeployGroups as qe,getParallelDestroyGroups as Je,getApplicationDeployOrder as We,getApplicationDestroyOrder as ze,getApplicationStackName as Ze,getOrganisationStackName as $e,isApplicationStack as er,isQuarantineDetail as rr,isRetainedBucketsDetail as tr,getApplicationStepName as or,getApplicationStepId as Er,toPascalCase as ar,isOpenNextPattern as ir,OPENNEXT_PATTERNS as sr,deriveResourcesFromManifestStacks as Sr,STACK_NOT_FOUND_PATTERN as nr,STACK_FAILED_STATE_PATTERN as cr,CDK_NO_STACKS_MATCH as Ar,INFRASTRUCTURE_FILENAME as Tr,ApplicationError as _r,wrapApplicationError as lr,FjallStateFileSchema as pr,readStateFile as Or,writeStateFile as Pr,createEmptyState as Rr,deleteStateFile as ur,updateTemplateHash as mr,getStateFilePath as Cr,stubCallerIdentity as Nr}from"./types/index.js";import{CloudFormationEventMonitor as Dr}from"./aws/index.js";import{CdkService as gr,CdkArgumentBuilder as fr,CdkProcessManager as Ir,CdkEventMonitor as xr,startStackMonitoring as Ur,DEFAULT_DEPLOY_TIMEOUT_MS as yr,isCdkError as Mr,formatInfrastructureError as vr,getStructuralHint as kr,getSourceContext as Yr,hasCdkDifferences as Vr,parseDiffOutput as hr,CloudFormationService as Fr,CloudFormationError as Br,EcsService as br,EcsError as Gr,EcsServiceResolver as Kr,TemplateHashService as Xr,TemplateHashError as Hr,CdkContextBuilder as wr,emitProgress as jr,PROGRESS_MESSAGES as Qr,parseBuildPhase as qr,buildStepContextBuildConfig as Jr,convertCloudFormationOutputsToRecord as Wr,ApplicationStackService as zr}from"./services/index.js";import{CdkError as $r}from"./types/errors/index.js";import{BaseServiceError as rt,ValidationError as tt,AuthError as ot,AwsError as Et,DeploymentError as at,NetworkError as it,FileSystemError as st,ConfigError as St,toServiceError as nt}from"./types/errors/index.js";import{filterDangerousEnvVars as At,maskSensitiveOutput as Tt,parseShellArgs as _t,sleep as lt}from"@fjall/util";import{hasDockerfile as Ot}from"./util/dockerfileDetection.js";import{createSequencedCallbacks as Rt}from"./util/sequencedCallbacks.js";import{fileExists as mt}from"@fjall/util/fsHelpers";import{success as Nt,failure as dt,isSuccess as Dt,isFailure as Lt}from"@fjall/generator";import{deploy as ft}from"./orchestration/index.js";import{destroy as xt}from"./orchestration/index.js";import{partitionAccounts as yt}from"./orchestration/index.js";import{buildRegionList as vt,buildAccountRegionPairs as kt,cascadeHomeRegion as Yt,cascadeOperationKey as Vt}from"./orchestration/index.js";import{projectScalarSummary as Ft,projectAccountRows as Bt}from"./orchestration/index.js";import{reconcileProviderAccounts as Gt,mergeReconciledProviderAccounts as Kt}from"./orchestration/index.js";import{decideNextTransition as Ht,reconcileTrailMigration as wt,decommissionMemberTrailStorage as jt,ORG_TRAIL_BUCKET_OUTPUT_KEY as Qt,TRAIL_BUCKET_OUTPUT_KEY as qt,TRAIL_KEY_ARN_OUTPUT_KEY as Jt}from"./orchestration/index.js";import{unlockBucket as zt,unlockQueue as Zt}from"./orchestration/index.js";import{triageBucketPolicy as eo,isEnforceSslStatement as ro,toResourcePolicyStatements as to,restoreBucketPolicy as oo,synthesiseEnforceSslDocument as Eo,ensureEnforceSsl as ao,restoreAndReconcileQuarantinedBucket as io}from"./orchestration/index.js";import{verifyOrgTrailDelivery as So}from"./aws/cloudtrail/orgTrailDelivery.js";import{assumeRootForTask as co,isRootTaskPolicyArn as Ao,ROOT_TASK_POLICY_ARNS as To,MAX_ROOT_SESSION_SECONDS as _o}from"./aws/sts/assumeRoot.js";import{parseAccountsConfiguration as po,flattenAccountsToEnvironments as Oo,extractAllAccountNames as Po,accountsConfigToOUTree as Ro,isStringArray as uo,isAccountsConfig as mo,isOuOnlyAccountBucket as Co,OU_ONLY_ACCOUNT_BUCKETS as No}from"./orchestration/index.js";import{runOpenNextBuild as Lo}from"./orchestration/index.js";import{runOrganisationSetup as fo,ORG_SETUP_PHASES as Io}from"./orchestration/index.js";import{resolveBuildSecrets as Uo,resolveSecretRefValue as yo,resolveBuildSecretSessionProvider as Mo,sourcedRefsFromBuildArgs as vo,resolveBuildArgs as ko,buildBuildSecretSessionPolicy as Yo,partitionForRegion as Vo,validateBuildGroup as ho}from"./orchestration/index.js";import{FrameworkRegistry as Bo}from"./orchestration/index.js";import{openNextBuilder as Go,dockerBuilder as Ko}from"./orchestration/index.js";import{StepRegistry as Ho,getDestroyStepId as wo}from"./steps/index.js";import{buildDeployPlan as Qo,computeDeployPlan as qo,computeAssemblyDigest as Jo,digestHead as Wo,classifyImpact as zo,derivePropertyChanges as Zo,isDataLoss as $o,isStatefulResourceType as eE,renderPlanSummary as rE,renderPlanLines as tE,toWirePlanChanges as oE,signApprovalToken as EE,verifyApprovalToken as aE,DEFAULT_APPROVAL_TTL_MS as iE,APPROVAL_TOKEN_PATTERN as sE,runApprovalGate as SE}from"./orchestration/index.js";export{X as ALL_PROGRESS_KINDS,Fe as APPLICATION_DEPLOY_ORDER,Be as APPLICATION_DESTROY_ORDER,Ve as APPLICATION_STACKS,K as APPROVAL_DECISIONS,G as APPROVAL_KINDS,sE as APPROVAL_TOKEN_PATTERN,_r as ApplicationError,zr as ApplicationStackService,ot as AuthError,Et as AwsError,h as BUILDERS,rt as BaseServiceError,v as CASCADE_ACCOUNT_PHASES,n as CASCADE_ACCOUNT_STATUSES,F as CASCADE_OUTCOME_RESULTS,S as CASCADE_PHASES,Ar as CDK_NO_STACKS_MATCH,fr as CdkArgumentBuilder,wr as CdkContextBuilder,$r as CdkError,xr as CdkEventMonitor,Ir as CdkProcessManager,gr as CdkService,O as ChangeCountsSchema,Br as CloudFormationError,Dr as CloudFormationEventMonitor,Fr as CloudFormationService,St as ConfigError,iE as DEFAULT_APPROVAL_TTL_MS,yr as DEFAULT_DEPLOY_TIMEOUT_MS,i as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,E as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,a as DEPLOYMENT_EVENT_STATUS_REASON_MAX,s as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,o as DEPLOYMENT_EVENT_TYPES,I as DEPLOY_EVENT_ARN_MAX,P as DEPLOY_EVENT_CONTRACT,R as DEPLOY_EVENT_CONTRACT_MAJOR,u as DEPLOY_EVENT_CONTRACT_MINOR,L as DEPLOY_EVENT_ERROR_MESSAGE_MAX,C as DEPLOY_EVENT_ID_MAX,d as DEPLOY_EVENT_MESSAGE_MAX,N as DEPLOY_EVENT_NAME_MAX,f as DEPLOY_EVENT_OUTPUT_MAX,D as DEPLOY_EVENT_STATUS_REASON_MAX,g as DEPLOY_EVENT_TRAIL_DETAIL_MAX,x as DEPLOY_EVENT_URL_MAX,k as DEPLOY_RESULTS,Y as DETECTION_PHASES,A as DeployEventSchema,_ as DeployResourceSchema,at as DeploymentError,t as DeploymentEventSchema,Gr as EcsError,br as EcsService,Kr as EcsServiceResolver,st as FileSystemError,pr as FjallStateFileSchema,Bo as FrameworkRegistry,Tr as INFRASTRUCTURE_FILENAME,Me as INFRASTRUCTURE_STEP_NAMES,ve as INFRA_STEP_NAME,y as LOG_LEVELS,_o as MAX_ROOT_SESSION_SECONDS,it as NetworkError,be as OPENNEXT_DEPLOY_ORDER,Ge as OPENNEXT_DESTROY_ORDER,He as OPENNEXT_PARALLEL_GROUPS,sr as OPENNEXT_PATTERNS,he as ORGANISATION_TYPES,Io as ORG_SETUP_PHASES,Qt as ORG_TRAIL_BUCKET_OUTPUT_KEY,No as OU_ONLY_ACCOUNT_BUCKETS,Ke as PARALLEL_DEPLOY_GROUPS,Xe as PARALLEL_DESTROY_GROUPS,we as PARALLEL_OPERATION_TYPES,V as PHASE_STATUSES,B as PLAN_ACTIONS,Qr as PROGRESS_MESSAGES,l as PlanChangeSchema,p as PlanPropertyChangeSchema,Ye as ProgressReporter,b as REPLACEMENT_MODES,fe as ROOT_ACCESS_FEATURES,To as ROOT_TASK_POLICY_ARNS,Ie as RootAccessEnablementError,Le as SECURITY_SERVICE_PRINCIPALS,ne as SERVICE_PRINCIPALS,M as STACK_CLEANUP_PHASES,cr as STACK_FAILED_STATE_PATTERN,nr as STACK_NOT_FOUND_PATTERN,U as STEP_COMPLETE_STATUSES,Ue as STEP_IDS,ye as STEP_NAMES,m as SUPPORTED_CONTRACT_MARKERS,q as SimpleAwsProvider,T as StepProgressSchema,Ho as StepRegistry,qt as TRAIL_BUCKET_OUTPUT_KEY,Jt as TRAIL_KEY_ARN_OUTPUT_KEY,Hr as TemplateHashError,Xr as TemplateHashService,tt as ValidationError,Ro as accountsConfigToOUTree,me as activateCostAllocationTags,Ae as activateTrustedAccess,co as assumeRootForTask,kt as buildAccountRegionPairs,ue as buildAccountToOUMap,Yo as buildBuildSecretSessionPolicy,Qo as buildDeployPlan,vt as buildRegionList,Jr as buildStepContextBuildConfig,$ as buildTargetSetUnreadyAdvisory,Z as buildTargetUnreadyAdvisory,Yt as cascadeHomeRegion,Vt as cascadeOperationKey,Ce as checkIdentityCentreStatus,te as checkRegionEnabled,W as checkTargetReadiness,zo as classifyImpact,Jo as computeAssemblyDigest,qo as computeDeployPlan,Wr as convertCloudFormationOutputsToRecord,Oe as createAccount,H as createDeployEmitter,Rr as createEmptyState,Rt as createSequencedCallbacks,Ht as decideNextTransition,jt as decommissionMemberTrailStorage,ur as deleteStateFile,ft as deploy,Zo as derivePropertyChanges,Sr as deriveResourcesFromManifestStacks,ie as describeOrganisation,z as describeTargetReadinessReason,xt as destroy,Wo as digestHead,Ko as dockerBuilder,jr as emitProgress,ge as enableCentralisedRootAccess,Te as enableIpamDelegatedAdmin,se as enablePolicyTypes,ce as enableRamSharing,Se as enableServiceAccess,ao as ensureEnforceSsl,ae as ensureOrganisationExists,Pe as ensureOrganisationalUnitsExist,Po as extractAllAccountNames,Ne as extractErrorName,dt as failure,mt as fileExists,At as filterDangerousEnvVars,pe as findAccount,Oo as flattenAccountsToEnvironments,vr as formatInfrastructureError,We as getApplicationDeployOrder,ze as getApplicationDestroyOrder,Ze as getApplicationStackName,Er as getApplicationStepId,or as getApplicationStepName,wo as getDestroyStepId,$e as getOrganisationStackName,qe as getParallelDeployGroups,Je as getParallelDestroyGroups,Yr as getSourceContext,Cr as getStateFilePath,kr as getStructuralHint,Vr as hasCdkDifferences,Ot as hasDockerfile,mo as isAccountsConfig,je as isApplicationOperation,er as isApplicationStack,Mr as isCdkError,$o as isDataLoss,ro as isEnforceSslStatement,Lt as isFailure,de as isOULeaf,ir as isOpenNextPattern,Qe as isOrganisationOperation,Co as isOuOnlyAccountBucket,rr as isQuarantineDetail,tr as isRetainedBucketsDetail,Ao as isRootTaskPolicyArn,eE as isStatefulResourceType,uo as isStringArray,Dt as isSuccess,le as listAccounts,Tt as maskSensitiveOutput,Kt as mergeReconciledProviderAccounts,Go as openNextBuilder,po as parseAccountsConfiguration,qr as parseBuildPhase,j as parseDeployContractVersion,w as parseDeployEvent,hr as parseDiffOutput,_t as parseShellArgs,yt as partitionAccounts,Vo as partitionForRegion,Re as placeAccountsInOUs,ee as probeAccountStackExists,Bt as projectAccountRows,Ft as projectScalarSummary,Or as readStateFile,Gt as reconcileProviderAccounts,wt as reconcileTrailMigration,oe as regionDisabledMessage,De as registerSecurityDelegates,tE as renderPlanLines,rE as renderPlanSummary,ko as resolveBuildArgs,Mo as resolveBuildSecretSessionProvider,Uo as resolveBuildSecrets,yo as resolveSecretRefValue,io as restoreAndReconcileQuarantinedBucket,oo as restoreBucketPolicy,SE as runApprovalGate,Lo as runOpenNextBuild,fo as runOrganisationSetup,EE as signApprovalToken,lt as sleep,vo as sourcedRefsFromBuildArgs,Ur as startStackMonitoring,Nr as stubCallerIdentity,Nt as success,Eo as synthesiseEnforceSslDocument,ar as toPascalCase,to as toResourcePolicyStatements,nt as toServiceError,oE as toWirePlanChanges,eo as triageBucketPolicy,zt as unlockBucket,Zt as unlockQueue,_e as updateBackupGlobalSettings,mr as updateTemplateHash,ho as validateBuildGroup,aE as verifyApprovalToken,So as verifyOrgTrailDelivery,lr as wrapApplicationError,Pr as writeStateFile};
@@ -1 +1 @@
1
- import{join as z}from"path";import{CloudFormationClient as Y}from"@aws-sdk/client-cloudformation";import{EC2Client as J}from"@aws-sdk/client-ec2";import{success as j,failure as g}from"@fjall/generator";import{logger as E}from"@fjall/util/logger";import{imageTagParameterName as Q,maskSensitiveOutput as S}from"@fjall/util";import{parseDockerServicesFromManifest as V}from"@fjall/util/manifest";import{stubCallerIdentity as X}from"../../types/deployment/index.js";import{getApplicationDeployOrder as Z,getApplicationStackName as ee,getApplicationStepName as G,getApplicationStepId as K,APPLICATION_STACKS as O}from"../../types/operations.js";import{CdkContextBuilder as te}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as oe,bootstrapOrFail as re}from"../contextHelpers.js";import{runDetectionPipeline as ne}from"./detectionPipeline.js";import{STEP_IDS as R,STEP_NAMES as ie}from"../../types/stepDefinitions.js";import{StepRegistry as ae}from"../../steps/stepRegistry.js";import{checkTargetReadiness as se}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as ce}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as le}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as W,runDockerBuild as de}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as pe,deployParallelPhase as ue,deployStackSequential as fe,runDockerPreCompute as me,createBuildCallbacks as ge}from"./applicationDeployHelpers.js";import{deployCodeOnly as ke}from"./codeOnlyDeploy.js";const T="applicationDeploy";function ye(a){const i=Object.entries(a);if(i.length===0)return;const r={};for(const[t,s]of i){const C=Q(t);r[C]=s}return r}async function Ue(a,i,r){const{callbacks:t,options:s}=a,C=Date.now(),D=te.buildDeploymentContext({deployType:"application",target:r.appName,path:r.path,region:i.awsProvider.getRegion(),callerIdentity:X(i.awsProvider.getAccountId()),...oe({orgConfig:a.orgConfig,identity:a.identity,skipOidc:a.options?.skipOidc})},{verbose:s?.verbose,infraOnly:s?.infraOnly},a.orgConfig),p=i.frameworkRegistry.resolve({appPath:r.path});let w;const k=!!s?.imageTag;if(p&&!k){w=p.builder.plan({appPath:r.path},p.detection);const e=ge(t),o=await p.builder.build(r.path,w,e,{skipBuild:s?.skipBuild,infraOnly:s?.infraOnly});if(!o.success){const n=new Error(S(o.error.message));return t.onError?.(n),g(n)}}if(s?.deployOnly||k){t.onLog?.(k?`Rollback mode \u2014 rolling to image tag ${s?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const e=z(r.path,"cdk.out"),o=V(e),n=o.length>0,d=p?.detection.hasDockerfile===!0,l=n||d;E.debug(T,"Deploy-only branch entered",{isRollback:k,imageTag:s?.imageTag,appName:r.appName,appPath:r.path,cdkOutPath:e,dockerProviderAvailable:a.dockerProvider!==void 0,builderName:p?.builder.name,hasDockerfileFromManifest:n,hasDockerfileFromDisk:d,hasDockerfile:l,manifestDockerServiceCount:o.length,manifestDockerPaths:o.map(m=>m.docker.path)}),!n&&!d&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");let u={};if(!k&&a.dockerProvider!==void 0&&l){E.debug(T,"Running Docker build before code-only deploy",{source:n?"manifest":"disk"});const m=await de(a,i,r,t);if(!m.success)return g(m.error);u=m.data}else E.debug(T,"Skipping Docker build",{reason:k?"rollback":a.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return ke(a,i,r,u)}t.onLog?.("Analysing infrastructure\u2026","info");const N=await ne(r,i,D,t);if(!N.success){const e=new Error(S(N.error.message));return t.onError?.(e),g(e)}const c=N.data;try{await t.onDetectionComplete?.({...c,builderName:p?.builder.name??"unknown"})}catch(e){const o=e instanceof Error?e.message:String(e),n=new Error(S(o));return t.onError?.(n),g(n)}const $={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:s?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...p&&{builderName:p.builder.name}},I=ae.getSteps($),v=I.findIndex(e=>e.id===R.TARGET_READINESS),L=await le(t,{stepId:R.TARGET_READINESS,stepName:ie.TARGET_READINESS,...v>=0&&{stepIndex:v,totalSteps:I.length}},async()=>{if(s?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const e=i.awsProvider.getAccountId(),o=a.orgConfig?.providerAccounts.find(u=>u.id===e)?.name,n=i.awsProvider.getCredentials(),d=ce(a.orgConfig),l=await se({cloudFormation:i.awsProvider.getClient(Y),ec2:new J({region:d,...n!==void 0&&{credentials:n}})},{id:e,...o!==void 0&&{name:o}},i.awsProvider.getRegion(),a.orgConfig,a.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(S(l.data.advisory))}:{kind:"error",error:new Error(S(l.error.message))}});if(!L.success)return L;const f=w?w.deployOrder:Z({pattern:c.pattern,resources:c.resources}),y=f.length,M=c.hasDockerfile&&a.dockerProvider!==void 0&&f.includes(O.COMPUTE)&&s?.infraOnly!==!0;if(!c.hasDifferences&&!s?.force&&!M){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(s?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const e=c.hasDockerfile&&a.dockerProvider!==void 0&&f.includes(O.COMPUTE);for(let n=0;n<f.length;n++){const d=f[n];e&&d===O.COMPUTE&&(t.onStepStart?.(R.DOCKER_OPERATIONS,W),t.onStepComplete?.(R.DOCKER_OPERATIONS,W,"skipped"));const l=K(d,"deploy"),u=G(d,"deploy");t.onStepStart?.(l,u,n,y),t.onStepComplete?.(l,u,"skipped",n,y)}const o=await i.stackService.resolveWebsiteUrl(r.appName);return j({target:r.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,durationMs:Date.now()-C})}const _=await re(i,D,t);if(!_.success)return _;const b={},P=new Map;for(let e=0;e<f.length;e++){const o=f[e],n=ee(r.appName,o),d=K(o,"deploy"),l=G(o,"deploy"),u=c.stackChanges.get(n)??!0,m=o===O.COMPUTE&&M;if(!u&&!s?.force&&!m){t.onStepStart?.(d,l,e,y),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(d,l,"skipped",e,y);continue}const A=pe(f,e,c.stackChanges,r.appName,s?.force);if(A.length>=2){const H=await ue(A,r,i,D,t,e,y,c,b,P);if(!H.success)return g(H.error);e+=A.length-1;continue}const h=await me(o,a,i,r,t,c.hasDockerfile);if(h!==null&&!h.success)return g(h.error);const q=h!==null&&h.success?h.data.contentHashTagsByService:{},B=ye(q),U=await fe(o,i,D,t,e,y,b,B!==void 0?{parameters:B}:void 0);if(!U.success)return g(U.error);const F=c.currentHashes.get(n);F&&P.set(n,F)}if(P.size>0){const e=await i.hashService.updateStateAfterDeploy(r.path,P);if(!e.success){const o=S(e.error.message);E.debug(T,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const x=await i.stackService.resolveWebsiteUrl(r.appName);return x&&(b.websiteUrl=x),j({target:r.appName,deploymentType:"application",outputs:Object.keys(b).length>0?b:void 0,durationMs:Date.now()-C})}export{Ue as deployApplication};
1
+ import{join as F}from"path";import{CloudFormationClient as Y}from"@aws-sdk/client-cloudformation";import{EC2Client as J}from"@aws-sdk/client-ec2";import{success as E,failure as m}from"@fjall/generator";import{logger as O}from"@fjall/util/logger";import{imageTagParameterName as Q,maskSensitiveOutput as k}from"@fjall/util";import{parseDockerServicesFromManifest as V}from"@fjall/util/manifest";import{stubCallerIdentity as X}from"../../types/deployment/index.js";import{getApplicationDeployOrder as Z,getApplicationStackName as ee,getApplicationStepName as K,getApplicationStepId as W,APPLICATION_STACKS as T}from"../../types/operations.js";import{CdkContextBuilder as te}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as oe,bootstrapOrFail as ne}from"../contextHelpers.js";import{runDetectionPipeline as re}from"./detectionPipeline.js";import{STEP_IDS as N,STEP_NAMES as ae}from"../../types/stepDefinitions.js";import{StepRegistry as ie}from"../../steps/stepRegistry.js";import{checkTargetReadiness as se}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as ce}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as le}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as $,runDockerBuild as de}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as pe,deployParallelPhase as ue,deployStackSequential as fe,runDockerPreCompute as ge,createBuildCallbacks as me}from"./applicationDeployHelpers.js";import{deployCodeOnly as ke}from"./codeOnlyDeploy.js";import{runApprovalGate as ye}from"./approvalGate.js";const R="applicationDeploy";function he(a){const i=Object.entries(a);if(i.length===0)return;const r={};for(const[t,s]of i){const y=Q(t);r[y]=s}return r}async function je(a,i,r){const{callbacks:t,options:s}=a,y=Date.now(),C=te.buildDeploymentContext({deployType:"application",target:r.appName,path:r.path,region:i.awsProvider.getRegion(),callerIdentity:X(i.awsProvider.getAccountId()),...oe({orgConfig:a.orgConfig,identity:a.identity,skipOidc:a.options?.skipOidc})},{verbose:s?.verbose,infraOnly:s?.infraOnly},a.orgConfig),p=i.frameworkRegistry.resolve({appPath:r.path});let D;const h=!!s?.imageTag;if(p&&!h){D=p.builder.plan({appPath:r.path},p.detection);const e=me(t),o=await p.builder.build(r.path,D,e,{skipBuild:s?.skipBuild,infraOnly:s?.infraOnly});if(!o.success){const n=new Error(k(o.error.message));return t.onError?.(n),m(n)}}if(s?.deployOnly||h){t.onLog?.(h?`Rollback mode \u2014 rolling to image tag ${s?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const e=F(r.path,"cdk.out"),o=V(e),n=o.length>0,d=p?.detection.hasDockerfile===!0,l=n||d;O.debug(R,"Deploy-only branch entered",{isRollback:h,imageTag:s?.imageTag,appName:r.appName,appPath:r.path,cdkOutPath:e,dockerProviderAvailable:a.dockerProvider!==void 0,builderName:p?.builder.name,hasDockerfileFromManifest:n,hasDockerfileFromDisk:d,hasDockerfile:l,manifestDockerServiceCount:o.length,manifestDockerPaths:o.map(g=>g.docker.path)}),!n&&!d&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");let u={};if(!h&&a.dockerProvider!==void 0&&l){O.debug(R,"Running Docker build before code-only deploy",{source:n?"manifest":"disk"});const g=await de(a,i,r,t);if(!g.success)return m(g.error);u=g.data}else O.debug(R,"Skipping Docker build",{reason:h?"rollback":a.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return ke(a,i,r,u)}t.onLog?.("Analysing infrastructure\u2026","info");const A=await re(r,i,C,t);if(!A.success){const e=new Error(k(A.error.message));return t.onError?.(e),m(e)}const c=A.data;try{await t.onDetectionComplete?.({...c,builderName:p?.builder.name??"unknown"})}catch(e){const o=e instanceof Error?e.message:String(e),n=new Error(k(o));return t.onError?.(n),m(n)}const q={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:s?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...p&&{builderName:p.builder.name}},I=ie.getSteps(q),M=I.findIndex(e=>e.id===N.TARGET_READINESS),L=await le(t,{stepId:N.TARGET_READINESS,stepName:ae.TARGET_READINESS,...M>=0&&{stepIndex:M,totalSteps:I.length}},async()=>{if(s?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const e=i.awsProvider.getAccountId(),o=a.orgConfig?.providerAccounts.find(u=>u.id===e)?.name,n=i.awsProvider.getCredentials(),d=ce(a.orgConfig),l=await se({cloudFormation:i.awsProvider.getClient(Y),ec2:new J({region:d,...n!==void 0&&{credentials:n}})},{id:e,...o!==void 0&&{name:o}},i.awsProvider.getRegion(),a.orgConfig,a.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(k(l.data.advisory))}:{kind:"error",error:new Error(k(l.error.message))}},a.abortSignal);if(!L.success)return L;const f=D?D.deployOrder:Z({pattern:c.pattern,resources:c.resources}),S=f.length,_=c.hasDockerfile&&a.dockerProvider!==void 0&&f.includes(T.COMPUTE)&&s?.infraOnly!==!0;if(!c.hasDifferences&&!s?.force&&!_){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(s?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const e=c.hasDockerfile&&a.dockerProvider!==void 0&&f.includes(T.COMPUTE);for(let n=0;n<f.length;n++){const d=f[n];e&&d===T.COMPUTE&&(t.onStepStart?.(N.DOCKER_OPERATIONS,$),t.onStepComplete?.(N.DOCKER_OPERATIONS,$,"skipped"));const l=W(d,"deploy"),u=K(d,"deploy");t.onStepStart?.(l,u,n,S),t.onStepComplete?.(l,u,"skipped",n,S)}const o=await i.stackService.resolveWebsiteUrl(r.appName);return E({target:r.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,durationMs:Date.now()-y})}if(a.approvalGate!==void 0||s?.approvalToken!==void 0&&s.approvalToken!==""||s?.planOnly===!0){const e=F(r.path,"cdk.out"),o=await ye({approvalGate:a.approvalGate,options:s??{},target:r.appName,cdkOutPath:e,detection:{stackChanges:c.stackChanges,currentHashes:c.currentHashes},cfnService:i.cfnService,callbacks:t});if(!o.success)return o;const n=o.data;if(n.kind==="rejected")return t.onLog?.(`Deploy rejected: ${k(n.reason)}`,"warn"),E({target:r.appName,deploymentType:"application",rejected:!0,durationMs:Date.now()-y});if(n.kind==="awaiting")return E({target:r.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:n.approvalToken,assemblyDigest:n.assemblyDigest,durationMs:Date.now()-y})}const x=await ne(i,C,t);if(!x.success)return x;const w={},P=new Map;for(let e=0;e<f.length;e++){const o=f[e],n=ee(r.appName,o),d=W(o,"deploy"),l=K(o,"deploy"),u=c.stackChanges.get(n)??!0,g=o===T.COMPUTE&&_;if(!u&&!s?.force&&!g){t.onStepStart?.(d,l,e,S),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(d,l,"skipped",e,S);continue}const v=pe(f,e,c.stackChanges,r.appName,s?.force);if(v.length>=2){const j=await ue(v,r,i,C,t,e,S,c,w,P);if(!j.success)return m(j.error);e+=v.length-1;continue}const b=await ge(o,a,i,r,t,c.hasDockerfile);if(b!==null&&!b.success)return m(b.error);const z=b!==null&&b.success?b.data.contentHashTagsByService:{},U=he(z),G=await fe(o,i,C,t,e,S,w,U!==void 0?{parameters:U}:void 0);if(!G.success)return m(G.error);const H=c.currentHashes.get(n);H&&P.set(n,H)}if(P.size>0){const e=await i.hashService.updateStateAfterDeploy(r.path,P);if(!e.success){const o=k(e.error.message);O.debug(R,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const B=await i.stackService.resolveWebsiteUrl(r.appName);return B&&(w.websiteUrl=B),E({target:r.appName,deploymentType:"application",outputs:Object.keys(w).length>0?w:void 0,durationMs:Date.now()-y})}export{je as deployApplication};
@@ -1,4 +1,4 @@
1
- import{success as h,failure as S}from"@fjall/generator";import{maskSensitiveOutput as w}from"@fjall/util";import{APPLICATION_STACKS as R,getApplicationStackName as C,getApplicationStepName as y,getApplicationStepId as N,PARALLEL_DEPLOY_GROUPS as A}from"../../types/operations.js";import{emitProgress as E}from"../../services/supporting/helpers.js";import{forwardOutput as O,forwardResourceProgress as L}from"../contextHelpers.js";import{runDockerBuild as I}from"./dockerBuildHelper.js";import{runWelcomeImageSetup as _}from"./welcomeImageHelper.js";import{withStepLifecycle as k}from"../stepLifecycle.js";function F(r,n,i,p,t){const l=new Set(A.PHASE_2.stacks),u=[];for(let o=n;o<r.length&&l.has(r[o]);o++)u.push(r[o]);if(u.length<2)return[];if(!t){const o=u.filter(f=>{const m=C(p,f);return i.get(m)!==!1});return o.length>=2?o:[]}return u}async function G(r,n,i,p,t,l,u,o,f,m){for(let e=0;e<r.length;e++){const s=r[e],c=N(s,"deploy"),d=y(s,"deploy");t.onStepStart?.(c,d,l+e,u)}E(t,"Deploying infrastructure in parallel\u2026"),t.onParallelPhaseStart?.(r,"Storage and database resources (parallel)");const a=await i.stackService.deployStacksInParallel(r,p,{onOutput:O(t),onResourceProgress:(e,s)=>{t.onResourceProgress?.(e),s&&t.onParallelStackResourceProgress?.(s,e)},onStackComplete:(e,s,c,d)=>{const P=N(e,"deploy"),B=y(e,"deploy"),x=l+r.indexOf(e);t.onStepComplete?.(P,B,s?"completed":"error",x,u),!s&&d&&t.onError?.(new Error(w(d.message)))}});if(!a.success)return t.onParallelPhaseComplete?.([]),S(a.error);const g=a.data.filter(e=>!e.success);if(t.onParallelPhaseComplete?.(a.data.map(e=>({stack:e.stack,success:e.success,error:e.error}))),g.length>0){const e=g.map(c=>c.stack).join(", "),s=g.map(c=>`${c.stack}: ${w(c.error?.message??"Unknown error")}`).join(`
1
+ import{success as h,failure as S}from"@fjall/generator";import{maskSensitiveOutput as C}from"@fjall/util";import{isAborted as R}from"../../aws/organisations/types.js";import{APPLICATION_STACKS as E,getApplicationStackName as O,getApplicationStepName as y,getApplicationStepId as N,PARALLEL_DEPLOY_GROUPS as L}from"../../types/operations.js";import{emitProgress as I}from"../../services/supporting/helpers.js";import{forwardOutput as B,forwardResourceProgress as _}from"../contextHelpers.js";import{runDockerBuild as k}from"./dockerBuildHelper.js";import{runWelcomeImageSetup as $}from"./welcomeImageHelper.js";import{withStepLifecycle as j}from"../stepLifecycle.js";function M(t,o,s,p,r){const c=new Set(L.PHASE_2.stacks),i=[];for(let n=o;n<t.length&&c.has(t[n]);n++)i.push(t[n]);if(i.length<2)return[];if(!r){const n=i.filter(f=>{const m=O(p,f);return s.get(m)!==!1});return n.length>=2?n:[]}return i}async function W(t,o,s,p,r,c,i,n,f,m){for(let e=0;e<t.length;e++){const a=t[e],l=N(a,"deploy"),d=y(a,"deploy");r.onStepStart?.(l,d,c+e,i)}I(r,"Deploying infrastructure in parallel\u2026"),r.onParallelPhaseStart?.(t,"Storage and database resources (parallel)");const u=await s.stackService.deployStacksInParallel(t,p,{onOutput:B(r),onResourceProgress:(e,a)=>{r.onResourceProgress?.(e),a&&r.onParallelStackResourceProgress?.(a,e)},onStackComplete:(e,a,l,d)=>{const P=N(e,"deploy"),x=y(e,"deploy"),A=c+t.indexOf(e),w=R(s.abortSignal);r.onStepComplete?.(P,x,a?"completed":w?"cancelled":"error",A,i),!a&&d&&!w&&r.onError?.(new Error(C(d.message)))}});if(!u.success)return r.onParallelPhaseComplete?.([]),S(u.error);const g=u.data.filter(e=>!e.success);if(r.onParallelPhaseComplete?.(u.data.map(e=>({stack:e.stack,success:e.success,error:e.error}))),g.length>0){const e=g.map(l=>l.stack).join(", "),a=g.map(l=>`${l.stack}: ${C(l.error?.message??"Unknown error")}`).join(`
2
2
  `);return S(new Error(`Failed to deploy stacks: ${e}
3
3
 
4
- ${s}`))}for(const e of a.data){if(e.success&&e.outputs)for(const[d,P]of Object.entries(e.outputs))f[d]=String(P);const s=C(n.appName,e.stack),c=o.currentHashes.get(s);c&&m.set(s,c)}return h(void 0)}async function K(r,n,i,p,t,l,u,o){const f=N(r,"deploy"),m=y(r,"deploy");return k(p,{stepId:f,stepName:m,stepIndex:t,totalSteps:l},async()=>{const a=await n.stackService.deployStack(r,i,{onOutput:O(p),onResourceProgress:L(p)},o?.parameters!==void 0?{parameters:o.parameters}:void 0);if(!a.success)return{kind:"error",error:a.error};if(a.data.outputs)for(const[g,e]of Object.entries(a.data.outputs))u[g]=String(e);return{kind:"completed",data:void 0}})}async function M(r,n,i,p,t,l){if(r!==R.COMPUTE||!n.dockerProvider)return null;if(l){const o=await I(n,i,p,t);return o.success?h({contentHashTagsByService:o.data}):S(o.error)}const u=await _(n,i,p,t);return u.success?h({contentHashTagsByService:{}}):S(u.error)}function W(r){return{onBuildStart:n=>{r.onOpenNextBuildStart?.(),r.onLog?.(`${n} build started`,"info")},onBuildProgress:(n,i)=>{r.onOpenNextProgress?.(i)},onBuildComplete:n=>{r.onOpenNextBuildComplete?.(),r.onLog?.(`${n} build complete`,"info")},onBuildError:(n,i)=>{r.onOpenNextBuildError?.(i)}}}export{W as createBuildCallbacks,G as deployParallelPhase,K as deployStackSequential,F as getParallelPhase2Stacks,M as runDockerPreCompute};
4
+ ${a}`))}for(const e of u.data){if(e.success&&e.outputs)for(const[d,P]of Object.entries(e.outputs))f[d]=String(P);const a=O(o.appName,e.stack),l=n.currentHashes.get(a);l&&m.set(a,l)}return h(void 0)}async function Y(t,o,s,p,r,c,i,n){const f=N(t,"deploy"),m=y(t,"deploy");return j(p,{stepId:f,stepName:m,stepIndex:r,totalSteps:c},async()=>{const u=await o.stackService.deployStack(t,s,{onOutput:B(p),onResourceProgress:_(p)},n?.parameters!==void 0?{parameters:n.parameters}:void 0);if(!u.success)return{kind:"error",error:u.error};if(u.data.outputs)for(const[g,e]of Object.entries(u.data.outputs))i[g]=String(e);return{kind:"completed",data:void 0}},o.abortSignal)}async function z(t,o,s,p,r,c){if(t!==E.COMPUTE||!o.dockerProvider)return null;if(c){const n=await k(o,s,p,r);return n.success?h({contentHashTagsByService:n.data}):S(n.error)}const i=await $(o,s,p,r);return i.success?h({contentHashTagsByService:{}}):S(i.error)}function J(t){return{onBuildStart:o=>{t.onOpenNextBuildStart?.(),t.onLog?.(`${o} build started`,"info")},onBuildProgress:(o,s)=>{t.onOpenNextProgress?.(s)},onBuildComplete:o=>{t.onOpenNextBuildComplete?.(),t.onLog?.(`${o} build complete`,"info")},onBuildError:(o,s)=>{t.onOpenNextBuildError?.(s)}}}export{J as createBuildCallbacks,W as deployParallelPhase,Y as deployStackSequential,M as getParallelPhase2Stacks,z as runDockerPreCompute};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The engine-level approval gate, run at the read→mutate boundary of an
3
+ * application deploy (after detection + the no-changes short-circuit, before
4
+ * bootstrap). It computes the typed plan, emits it, and resolves an
5
+ * approve / reject / await decision through the injected surface resolver.
6
+ *
7
+ * Fully backward-compatible: when no gate is engaged the boundary is skipped and
8
+ * the deploy proceeds exactly as before.
9
+ *
10
+ * Minimal-viable emission: the plan is surfaced over the LEGACY DeployCallbacks
11
+ * (`onLog`). Rich contract-event emission (`plan.available` / `approval.awaiting`
12
+ * / `approval.resolved`) is deferred to the emitter migration (Brief 2 Phases
13
+ * 2–3) — the resolver already receives the full typed plan.
14
+ */
15
+ import { type Result } from "@fjall/generator";
16
+ import type { DeployCallbacks } from "../../types/callbacks.js";
17
+ import type { DeployOptions } from "../../types/params.js";
18
+ import type { ApprovalGate } from "../../types/approval.js";
19
+ import { type CfnTemplateReader } from "./plan/computeDeployPlan.js";
20
+ export interface RunApprovalGateParams {
21
+ /** The surface resolver. Optional — a token-resume or plan-only run needs none. */
22
+ approvalGate?: ApprovalGate;
23
+ options: DeployOptions;
24
+ target: string;
25
+ cdkOutPath: string;
26
+ detection: {
27
+ stackChanges: ReadonlyMap<string, boolean>;
28
+ currentHashes: ReadonlyMap<string, string>;
29
+ };
30
+ cfnService: CfnTemplateReader;
31
+ callbacks: DeployCallbacks;
32
+ }
33
+ export type ApprovalGateOutcome = {
34
+ kind: "proceed";
35
+ } | {
36
+ kind: "rejected";
37
+ reason: string;
38
+ } | {
39
+ kind: "awaiting";
40
+ approvalToken: string;
41
+ assemblyDigest: string;
42
+ expiresAt: string;
43
+ };
44
+ export declare function runApprovalGate(params: RunApprovalGateParams): Promise<Result<ApprovalGateOutcome, Error>>;
@@ -0,0 +1 @@
1
+ import{join as f}from"node:path";import{readFile as k}from"node:fs/promises";import{success as o,failure as d}from"@fjall/generator";import{maskSensitiveOutput as v}from"@fjall/util";import{computeAssemblyDigest as g}from"./plan/assemblyDigest.js";import{computeDeployPlan as m}from"./plan/computeDeployPlan.js";import{renderPlanLines as h}from"./plan/renderDeployPlan.js";import{signApprovalToken as y,verifyApprovalToken as w}from"./plan/approvalToken.js";function A(e){switch(e){case"superseded":return"the plan changed since it was approved (re-plan and approve again)";case"expired":return"the approval window expired";case"tampered":return"the approval signature did not verify";case"malformed":return"the approval token was malformed"}}async function x(e){const t=[];for(const n of e.changedStacks){const c=f(e.cdkOutPath,`${n}.template.json`);let i;try{i=JSON.parse(await k(c,"utf8"))}catch(r){return d(new Error(v(`Failed to read synthesised template for ${n}: ${r instanceof Error?r.message:String(r)}`)))}t.push({stackName:n,newTemplate:i})}return m({cfnService:e.cfnService,stacks:t,assemblyDigest:e.assemblyDigest})}function S(e,t){e.onLog?.(`Deploy plan: ${t.summary}`,"info");for(const n of h(t))e.onLog?.(n,"info")}async function C(e){const t=g(e.detection.currentHashes),n=e.options.approvalToken;if(n!==void 0&&n!==""){const s=w({token:n,assemblyDigest:t});if(s.ok)return e.callbacks.onLog?.("Approval token verified against the current plan \u2014 proceeding","info"),o({kind:"proceed"});const l=A(s.reason);return e.callbacks.onLog?.(`Approval refused: ${l}`,"warn"),o({kind:"rejected",reason:l})}const c=[...e.detection.stackChanges.entries()].filter(([,s])=>s).map(([s])=>s),i=await x({cfnService:e.cfnService,cdkOutPath:e.cdkOutPath,changedStacks:c,assemblyDigest:t});if(!i.success)return d(i.error);const r=i.data;S(e.callbacks,r);const a=y({assemblyDigest:t});if(e.options.autoApprove===!0)return e.callbacks.onLog?.("Plan auto-approved (--auto-approve)","info"),o({kind:"proceed"});if(e.options.planOnly===!0)return o({kind:"awaiting",approvalToken:a.token,assemblyDigest:t,expiresAt:a.expiresAt});if(e.approvalGate===void 0)return o({kind:"awaiting",approvalToken:a.token,assemblyDigest:t,expiresAt:a.expiresAt});const u={target:e.target,plan:r,approvalToken:a.token,expiresAt:a.expiresAt,hasDestructiveChanges:r.hasDestructiveChanges},p=await e.approvalGate.resolve(u);switch(p.kind){case"approved":return o({kind:"proceed"});case"rejected":return o({kind:"rejected",reason:p.reason});case"suspended":return o({kind:"awaiting",approvalToken:p.approvalToken,assemblyDigest:t,expiresAt:a.expiresAt})}}export{C as runApprovalGate};
@@ -0,0 +1,72 @@
1
+ import { type Result } from "@fjall/generator";
2
+ import type { DockerBuildArgValue } from "@fjall/util/manifest/schemas";
3
+ /**
4
+ * Resolve a sourced (`{ ssm | secretsManager | env }`) build-arg value to its
5
+ * string value. Threaded in from the caller so this resolver stays free of an
6
+ * AWS dependency; the CLI/worker pass the scoped-session reader (sub-deliverable
7
+ * C) and the resolver only calls it when no higher precedence tier shadows the
8
+ * key.
9
+ */
10
+ export type SourcedBuildArgResolver = (key: string, source: Exclude<DockerBuildArgValue, string>) => Promise<Result<string>>;
11
+ export interface ResolveBuildArgsContext {
12
+ /**
13
+ * The `docker.buildArgs` declared in the manifest (lowest precedence). Only
14
+ * keys present here (∪ flag keys) are ever emitted — the resolver never
15
+ * injects arbitrary host env.
16
+ */
17
+ manifestBuildArgs?: Record<string, DockerBuildArgValue>;
18
+ /** Build-context directory holding `.env*` files (`identity.buildContext`). */
19
+ serviceDir: string;
20
+ /** App-dir fallback when a `.env*` file is absent from `serviceDir`. */
21
+ appDir?: string;
22
+ /** Deployment stage, used for the `.env.<stage>` tier (e.g. "production"). */
23
+ stage?: string;
24
+ /** The build-host shell environment (usually `process.env`). */
25
+ shellEnv: Record<string, string | undefined>;
26
+ /**
27
+ * Phase-4 CLI `--build-arg` override seam. Today the callers pass undefined /
28
+ * {}. Highest precedence; its keys widen the emitted set beyond the manifest.
29
+ */
30
+ flagBuildArgs?: Record<string, string>;
31
+ /**
32
+ * Resolves a sourced manifest value to a string. Required only when the
33
+ * manifest declares a sourced value that no higher tier shadows; absent it
34
+ * (or a sourced value reaching it being unresolvable) fails closed.
35
+ */
36
+ resolveSourcedValue?: SourcedBuildArgResolver;
37
+ }
38
+ export interface ResolveBuildArgsResult {
39
+ /** The resolved `KEY=VALUE` map for `DockerBuildParams.buildArgs`. */
40
+ buildArgs: Record<string, string>;
41
+ /**
42
+ * Keys auto-declared because a public-prefixed key was found in a parsed
43
+ * `.env`/`.env.<stage>` FILE (not explicit in the manifest, not a flag). The
44
+ * caller surfaces an R3-style inverse-footgun warning for each (masked at the
45
+ * sink) — auto-baking a `.env` key freezes it into the image.
46
+ */
47
+ inferredDotenvKeys: string[];
48
+ }
49
+ /**
50
+ * Resolve `docker.buildArgs` to a flat `Record<string, string>` for
51
+ * `DockerBuildParams`, applying the source-precedence ladder (highest → lowest):
52
+ *
53
+ * flagBuildArgs > shell env `KEY` > `.env.<stage>` > `.env`/`.env.production`
54
+ * > manifest literal/sourced value
55
+ *
56
+ * Keys declared in `manifestBuildArgs` (∪ `flagBuildArgs`) are always emitted.
57
+ * Phase 3 ADDITIONALLY auto-declares a key that is BOTH public-prefixed
58
+ * (`isPublicBuildVarName`) AND present in a parsed `.env`/`.env.<stage>` FILE —
59
+ * never from raw shell env. Shell remains a VALUE-override tier for already
60
+ * declared/inferred keys; it is never a KEY source, so arbitrary host env can
61
+ * never be injected as a build-arg.
62
+ *
63
+ * An empty string at ANY tier is treated as ABSENT and falls through to the
64
+ * next tier (the 2026-06-10 Turnstile empty-string shadow shape); deploy-core
65
+ * has no `getEnv`, so the check is inline `raw !== undefined && raw !== ""`.
66
+ *
67
+ * A manifest value that is a sourced reference (`{ ssm | secretsManager | env }`)
68
+ * is resolved via `resolveSourcedValue` ONLY when no higher tier supplied a
69
+ * value for that key. A plain-string manifest value is used verbatim. An
70
+ * auto-declared `.env` key carries no manifest value — the ladder supplies it.
71
+ */
72
+ export declare function resolveBuildArgs(ctx: ResolveBuildArgsContext): Promise<Result<ResolveBuildArgsResult>>;
@@ -0,0 +1 @@
1
+ import{readFile as k}from"node:fs/promises";import{join as A}from"node:path";import{success as h,failure as g}from"@fjall/generator";import{getErrorMessage as V,maskSensitiveOutput as B}from"@fjall/util";import{isPublicBuildVarName as D}from"@fjall/util/docker";async function $(e){const o=e.manifestBuildArgs??{},a=e.flagBuildArgs??{};let r,i;try{r=await w(e,[".env",".env.production"]),i=e.stage!==void 0&&e.stage!==""?await w(e,[`.env.${e.stage}`]):{}}catch(n){return g(new Error(`Failed to read .env build-arg sources: ${B(V(n))}`))}const s=new Set([...Object.keys(o),...Object.keys(a)]),f=[];for(const n of new Set([...Object.keys(i),...Object.keys(r)]))s.has(n)||D(n)&&f.push(n);const t=new Set([...s,...f]);if(t.size===0)return h({buildArgs:{},inferredDotenvKeys:[]});const u={};for(const n of t){const p=c(a[n]);if(p!==void 0){u[n]=p;continue}const m=c(e.shellEnv[n]);if(m!==void 0){u[n]=m;continue}const v=c(i[n]);if(v!==void 0){u[n]=v;continue}const y=c(r[n]);if(y!==void 0){u[n]=y;continue}const d=o[n];if(d===void 0)continue;if(typeof d=="string"){const b=c(d);b!==void 0&&(u[n]=b);continue}if(e.resolveSourcedValue===void 0)return g(new Error(`buildArg "${n}" is sourced from a secret store but no resolver was provided to read it.`));const l=await e.resolveSourcedValue(n,d);if(!l.success)return g(l.error);u[n]=l.data}return h({buildArgs:u,inferredDotenvKeys:f})}function c(e){return e!==void 0&&e!==""?e:void 0}async function w(e,o){const a=[e.serviceDir,...e.appDir?[e.appDir]:[]];for(const r of a)for(const i of o){const s=await S(A(r,i));if(s!==void 0)return O(s)}return{}}async function S(e){try{return await k(e,"utf8")}catch{return}}function O(e){const o={};for(const a of e.split(/\r?\n/)){const r=a.trim();if(r===""||r.startsWith("#"))continue;const i=r.replace(/^export\s+/,""),s=i.indexOf("=");if(s<=0)continue;const f=i.slice(0,s).trim();if(f==="")continue;let t=i.slice(s+1).trim();t.length>=2&&(t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'"))&&(t=t.slice(1,-1)),o[f]=t}return o}export{$ as resolveBuildArgs};
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Fail-closed structural validation for a single build group, run BEFORE the
3
+ * bake-guard and before any docker input is resolved. It is the anti-silent
4
+ * -secret-drop gate: both build entry points (`dockerBuildHelper.buildGroup`
5
+ * and `EcrBuildOrchestrator.buildGroupViaBuildx`) resolve only the LEADER's
6
+ * `(buildArgs, buildSecrets)`, so a member of the same build group that
7
+ * declares DIFFERENT docker inputs would ship an artefact that disagrees with
8
+ * what it declared — invisibly. This validator rejects that, plus the
9
+ * collision classes that would otherwise corrupt the buildx argv or the
10
+ * temp-file mount.
11
+ *
12
+ * Every rejection message names the offending IDENTIFIER (a key, a secret id,
13
+ * a service name, a path) — never a secret VALUE (`security-standards.md §
14
+ * "Values, not identifiers"`). The inputs reaching this validator are manifest
15
+ * references, not resolved secrets, so nothing here is value-bearing.
16
+ *
17
+ * Rules (each maps to a brief-named class):
18
+ * - C-f: a `buildArgs` KEY equal to a `buildSecrets[].id` — the public ARG
19
+ * would shadow the secret mount (ambiguous which channel the Dockerfile
20
+ * reads).
21
+ * - C-g: a duplicate `buildSecrets[].id` within the group — the resolver
22
+ * writes each value to `join(tempDir, ref.id)`, so the second collides and
23
+ * silently wins.
24
+ * - C-h: a `buildSecrets[].id` not matching `BUILDKIT_SECRET_ID_PATTERN`
25
+ * (defence-in-depth — parse catches manifest-sourced ids, but a direct
26
+ * deploy-core caller can construct refs without going through the schema).
27
+ * - C-i: `buildContext` is not an existing DIRECTORY.
28
+ * - C-k: a grouped non-leader member whose normalised `(buildArgs,
29
+ * buildSecrets)` differs from the leader's — the silent secret-drop.
30
+ *
31
+ * No `target`-validity check: buildx validates the target stage itself, and a
32
+ * pre-check here would be both redundant and a second source of truth.
33
+ */
34
+ import { type Result } from "@fjall/generator";
35
+ import { type DockerBuildArgValue, type DockerBuildSecretRef } from "@fjall/util/manifest/schemas";
36
+ /**
37
+ * One member's resolvable docker inputs, used by the C-k leader-parity check.
38
+ * Carries only the two channels that are resolved per-group from the leader —
39
+ * the fields whose divergence ships a different artefact than declared.
40
+ */
41
+ export interface BuildGroupMemberInputs {
42
+ serviceName: string;
43
+ buildArgs?: Record<string, DockerBuildArgValue>;
44
+ buildSecrets?: readonly DockerBuildSecretRef[];
45
+ }
46
+ export interface ValidateBuildGroupInput {
47
+ /** The group leader's service name — used only for messaging. */
48
+ serviceName: string;
49
+ dockerfilePath: string;
50
+ buildContext: string;
51
+ target?: string;
52
+ /** The leader's declared buildArgs (manifest references, not resolved). */
53
+ buildArgs?: Record<string, DockerBuildArgValue>;
54
+ /** The leader's declared buildSecrets (manifest references, not resolved). */
55
+ buildSecrets?: readonly DockerBuildSecretRef[];
56
+ /**
57
+ * Every member of the group (including the leader). Each member's
58
+ * `(buildArgs, buildSecrets)` is compared to the leader's top-level inputs;
59
+ * any divergence is the C-k silent secret-drop. When omitted, the C-k check
60
+ * is skipped (single-member groups never drift).
61
+ */
62
+ members?: readonly BuildGroupMemberInputs[];
63
+ }
64
+ export declare function validateBuildGroup(input: ValidateBuildGroupInput): Result<void, Error>;
@@ -0,0 +1 @@
1
+ import{existsSync as a,statSync as u}from"node:fs";import{success as f,failure as t}from"@fjall/generator";import{BUILDKIT_SECRET_ID_PATTERN as b}from"@fjall/util/manifest/schemas";function c(e){return n({buildArgs:e.buildArgs??null,buildSecrets:e.buildSecrets??null})}function n(e){return Array.isArray(e)?`[${e.map(n).join(",")}]`:e!==null&&typeof e=="object"?`{${Object.keys(e).sort().map(i=>`${JSON.stringify(i)}:${n(e[i])}`).join(",")}}`:JSON.stringify(e)}function w(e){const o=e.buildSecrets??[];for(const r of o)if(!b.test(r.id))return t(new Error(`Build secret id "${r.id}" for service '${e.serviceName}' contains characters outside the allowed BuildKit set [A-Za-z0-9_.-]; rename it (no comma, '=', or whitespace, which break the buildx --secret argv).`));const i=new Set;for(const r of o){if(i.has(r.id))return t(new Error(`Duplicate build secret id "${r.id}" for service '${e.serviceName}'; each buildSecrets[].id must be unique within a build group (the resolver writes each value to a temp file keyed by id, so a duplicate is silently overwritten).`));i.add(r.id)}const l=e.buildArgs??{};for(const r of Object.keys(l))if(i.has(r))return t(new Error(`buildArg key "${r}" for service '${e.serviceName}' collides with a buildSecrets[].id; a public --build-arg would shadow the secret mount of the same name. Rename one of them.`));if(!a(e.buildContext)||!u(e.buildContext).isDirectory())return t(new Error(`Build context "${e.buildContext}" for service '${e.serviceName}' is not an existing directory.`));const d=e.members;if(d!==void 0&&d.length>0){const r=c({serviceName:e.serviceName,...e.buildArgs!==void 0&&{buildArgs:e.buildArgs},...e.buildSecrets!==void 0&&{buildSecrets:e.buildSecrets}});for(const s of d)if(s.serviceName!==e.serviceName&&c(s)!==r)return t(new Error(`Service '${s.serviceName}' shares a build group with leader '${e.serviceName}' but declares different docker buildArgs/buildSecrets. Only the group leader's docker inputs are resolved and built, so '${s.serviceName}' would ship an image built with the leader's inputs, not its own. Give it a distinct Dockerfile path, context, or target so it builds independently, or align its buildArgs/buildSecrets with the leader.`))}return f(void 0)}export{w as validateBuildGroup};