@fjall/deploy-core 2.23.0 → 2.24.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 (40) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/index.d.ts +2 -2
  3. package/dist/src/aws/index.js +1 -1
  4. package/dist/src/aws/organisations/identityCentreSnapshot.d.ts +47 -0
  5. package/dist/src/aws/organisations/identityCentreSnapshot.js +1 -0
  6. package/dist/src/aws/organisations/index.d.ts +5 -1
  7. package/dist/src/aws/organisations/index.js +1 -1
  8. package/dist/src/aws/organisations/pagination.d.ts +28 -0
  9. package/dist/src/aws/organisations/pagination.js +1 -0
  10. package/dist/src/index.d.ts +7 -5
  11. package/dist/src/index.js +1 -1
  12. package/dist/src/orchestration/application/applicationDeploy.js +1 -1
  13. package/dist/src/orchestration/application/applicationDeployHelpers.d.ts +11 -12
  14. package/dist/src/orchestration/application/applicationDeployHelpers.js +2 -2
  15. package/dist/src/orchestration/application/approvalGate.d.ts +3 -1
  16. package/dist/src/orchestration/application/approvalGate.js +1 -1
  17. package/dist/src/orchestration/application/artefactCollection.d.ts +18 -0
  18. package/dist/src/orchestration/application/artefactCollection.js +1 -0
  19. package/dist/src/orchestration/application/buildSecretSession.d.ts +8 -0
  20. package/dist/src/orchestration/application/buildSecretSession.js +1 -1
  21. package/dist/src/orchestration/application/codeOnlyDeploy.d.ts +7 -0
  22. package/dist/src/orchestration/application/codeOnlyDeploy.js +1 -1
  23. package/dist/src/orchestration/application/deploySessionPolicy.d.ts +46 -0
  24. package/dist/src/orchestration/application/deploySessionPolicy.js +1 -0
  25. package/dist/src/orchestration/application/dockerBuildHelper.d.ts +21 -5
  26. package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
  27. package/dist/src/orchestration/deploy.js +1 -1
  28. package/dist/src/orchestration/dockerInterface.d.ts +5 -2
  29. package/dist/src/orchestration/index.d.ts +1 -0
  30. package/dist/src/orchestration/index.js +1 -1
  31. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +1 -1
  32. package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -1
  33. package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -2
  34. package/dist/src/services/infrastructure/CdkService.d.ts +1 -1
  35. package/dist/src/services/infrastructure/CdkService.js +2 -2
  36. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +9 -0
  37. package/dist/src/types/approval.d.ts +15 -0
  38. package/dist/src/types/index.d.ts +1 -1
  39. package/dist/src/types/params.d.ts +31 -1
  40. package/package.json +5 -4
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 174 files minified at 2026-07-02T09:02:52.552Z
1
+ 178 files minified at 2026-07-05T07:50:53.066Z
@@ -6,7 +6,7 @@ export { buildTargetSetUnreadyAdvisory } from "./targetSetAdvisory.js";
6
6
  export type { TargetSetUnreadyAdvisoryParams } from "./targetSetAdvisory.js";
7
7
  export { checkRegionEnabled, regionDisabledMessage } from "./regions/regionOptStatus.js";
8
8
  export type { RegionAvailability } from "./regions/regionOptStatus.js";
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";
10
- export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./organisations/index.js";
9
+ export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, snapshotIdentityCentre, composeSdkAbortSignal, extractErrorName, isAborted, isOULeaf, DEFAULT_MAX_PAGES, IDENTITY_STORE_PAGE_SIZE, drainPages, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./organisations/index.js";
10
+ export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, DrainPagesOptions, PagedResult, IdentityCentreStatus, IdentityCentreSnapshot, IdentityCentreSnapshotGroup, IdentityCentreSnapshotOptions, IdentityCentreSnapshotResult, IdentityCentreSnapshotUser, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./organisations/index.js";
11
11
  export { CloudFormationEventMonitor } from "./utils/cloudformationEvents.js";
12
12
  export type { AwsClientLike, EventLogWriter, EventFailureAnalyser, EventLogWriterFactory, EventMonitorDeps } 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{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};
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 d}from"./targetSetAdvisory.js";import{checkRegionEnabled as A,regionDisabledMessage as E}from"./regions/regionOptStatus.js";import{ensureOrganisationExists as g,describeOrganisation as b,enablePolicyTypes as u,enableServiceAccess as p,SERVICE_PRINCIPALS as R,enableRamSharing as T,activateTrustedAccess as m,enableIpamDelegatedAdmin as I,updateBackupGlobalSettings as C,listAccounts as x,findAccount as _,createAccount as y,ensureOrganisationalUnitsExist as O,placeAccountsInOUs as P,buildAccountToOUMap as U,activateCostAllocationTags as f,checkIdentityCentreStatus as v,snapshotIdentityCentre as k,composeSdkAbortSignal as h,extractErrorName as D,isAborted as L,isOULeaf as M,DEFAULT_MAX_PAGES as N,IDENTITY_STORE_PAGE_SIZE as F,drainPages as G,registerSecurityDelegates as V,SECURITY_SERVICE_PRINCIPALS as Y,enableCentralisedRootAccess as w,ROOT_ACCESS_FEATURES as B,RootAccessEnablementError as X}from"./organisations/index.js";import{CloudFormationEventMonitor as j}from"./utils/cloudformationEvents.js";export{j as CloudFormationEventMonitor,N as DEFAULT_MAX_PAGES,F as IDENTITY_STORE_PAGE_SIZE,B as ROOT_ACCESS_FEATURES,X as RootAccessEnablementError,Y as SECURITY_SERVICE_PRINCIPALS,R as SERVICE_PRINCIPALS,a as SimpleAwsProvider,f as activateCostAllocationTags,m as activateTrustedAccess,U as buildAccountToOUMap,d as buildTargetSetUnreadyAdvisory,n as buildTargetUnreadyAdvisory,v as checkIdentityCentreStatus,A as checkRegionEnabled,s as checkTargetReadiness,h as composeSdkAbortSignal,y as createAccount,b as describeOrganisation,o as describeTargetReadinessReason,G as drainPages,w as enableCentralisedRootAccess,I as enableIpamDelegatedAdmin,u as enablePolicyTypes,T as enableRamSharing,p as enableServiceAccess,g as ensureOrganisationExists,O as ensureOrganisationalUnitsExist,D as extractErrorName,_ as findAccount,L as isAborted,M as isOULeaf,x as listAccounts,P as placeAccountsInOUs,i as probeAccountStackExists,E as regionDisabledMessage,V as registerSecurityDelegates,k as snapshotIdentityCentre,C as updateBackupGlobalSettings};
@@ -0,0 +1,47 @@
1
+ import { type IdentitystoreClient } from "@aws-sdk/client-identitystore";
2
+ import { type SSOAdminClient } from "@aws-sdk/client-sso-admin";
3
+ import { type Result } from "@fjall/generator";
4
+ /**
5
+ * Read-only Identity Centre snapshot for post-deploy consumers (the webapp
6
+ * deployment worker persists it to map org members to AWS access by email).
7
+ * Design: aiDocs/designs/2026-07-04-org-user-management-design.md § W6.
8
+ */
9
+ export interface IdentityCentreSnapshotUser {
10
+ userId: string;
11
+ userName?: string;
12
+ email?: string;
13
+ displayName?: string;
14
+ }
15
+ export interface IdentityCentreSnapshotGroup {
16
+ groupId: string;
17
+ displayName?: string;
18
+ description?: string;
19
+ memberUserIds: string[];
20
+ }
21
+ export interface IdentityCentreSnapshot {
22
+ instanceArn: string;
23
+ identityStoreId: string;
24
+ users: IdentityCentreSnapshotUser[];
25
+ groups: IdentityCentreSnapshotGroup[];
26
+ /** True when any listing stopped at the page budget — counts understate. */
27
+ truncated: boolean;
28
+ }
29
+ export type IdentityCentreSnapshotResult = {
30
+ status: "no-instance";
31
+ } | {
32
+ status: "captured";
33
+ snapshot: IdentityCentreSnapshot;
34
+ };
35
+ export interface IdentityCentreSnapshotOptions {
36
+ abortSignal?: AbortSignal;
37
+ /** Page budget per listing call family. Default: DEFAULT_MAX_PAGES in pagination.ts (~1000 items at 50/page). */
38
+ maxPages?: number;
39
+ }
40
+ /**
41
+ * Capture the live Identity Centre state: instance, users (with emails),
42
+ * groups, and group memberships. All reads are paginated under a page budget
43
+ * and abort-composed; truncation is reported, never silent. Zero instances is
44
+ * a first-class outcome ("no-instance"), not an error — orgs without Identity
45
+ * Centre enabled are expected.
46
+ */
47
+ export declare function snapshotIdentityCentre(ssoAdminClient: SSOAdminClient, identitystoreClient: IdentitystoreClient, options?: IdentityCentreSnapshotOptions): Promise<Result<IdentityCentreSnapshotResult>>;
@@ -0,0 +1 @@
1
+ import{ListGroupMembershipsCommand as N,ListGroupsCommand as M,ListUsersCommand as w}from"@aws-sdk/client-identitystore";import{ListInstancesCommand as x}from"@aws-sdk/client-sso-admin";import{success as g,failure as o}from"@fjall/generator";import{getErrorMessage as T,maskSensitiveOutput as U}from"@fjall/util";import{composeSdkAbortSignal as u,isAborted as h}from"./types.js";import{IDENTITY_STORE_PAGE_SIZE as p,drainPages as f}from"./pagination.js";function m(c,s){return new Error(`Failed to ${c}: ${U(T(s))}`)}async function L(c,s,n={}){let y,a;try{const r=((await c.send(new x({}),{abortSignal:u(n.abortSignal)})).Instances??[])[0];if(r?.InstanceArn===void 0||r.IdentityStoreId===void 0)return g({status:"no-instance"});y=r.InstanceArn,a=r.IdentityStoreId}catch(t){return o(m("list Identity Centre instances",t))}let l;try{l=await f(async t=>{const r=await s.send(new w({IdentityStoreId:a,MaxResults:p,...t!==void 0&&{NextToken:t}}),{abortSignal:u(n.abortSignal)});return{items:(r.Users??[]).flatMap(e=>e.UserId===void 0?[]:[{userId:e.UserId,...e.UserName!==void 0&&{userName:e.UserName},...e.Emails?.[0]?.Value!==void 0&&{email:e.Emails[0].Value},...e.DisplayName!==void 0&&{displayName:e.DisplayName}}]),nextToken:r.NextToken}},n)}catch(t){return o(m("list identity store users",t))}let I;try{I=await f(async t=>{const r=await s.send(new M({IdentityStoreId:a,MaxResults:p,...t!==void 0&&{NextToken:t}}),{abortSignal:u(n.abortSignal)});return{items:(r.Groups??[]).flatMap(e=>e.GroupId===void 0?[]:[{groupId:e.GroupId,...e.DisplayName!==void 0&&{displayName:e.DisplayName},...e.Description!==void 0&&{description:e.Description}}]),nextToken:r.NextToken}},n)}catch(t){return o(m("list identity store groups",t))}const S=[];let i=!1;for(const t of I.items){if(h(n.abortSignal)){i=!0;break}let r;try{r=await f(async e=>{const b=await s.send(new N({IdentityStoreId:a,GroupId:t.groupId,MaxResults:p,...e!==void 0&&{NextToken:e}}),{abortSignal:u(n.abortSignal)});return{items:(b.GroupMemberships??[]).flatMap(d=>d.MemberId!==void 0&&"UserId"in d.MemberId&&d.MemberId.UserId!==void 0?[d.MemberId.UserId]:[]),nextToken:b.NextToken}},n)}catch(e){return o(m(`list memberships for group "${t.displayName??t.groupId}"`,e))}i=i||r.truncated,S.push({...t,memberUserIds:r.items})}return g({status:"captured",snapshot:{instanceArn:y,identityStoreId:a,users:l.items,groups:S,truncated:l.truncated||I.truncated||i}})}export{L as snapshotIdentityCentre};
@@ -1,5 +1,7 @@
1
1
  export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus } from "./types.js";
2
- export { extractErrorName, isOULeaf } from "./types.js";
2
+ export { composeSdkAbortSignal, extractErrorName, isAborted, isOULeaf } from "./types.js";
3
+ export { DEFAULT_MAX_PAGES, IDENTITY_STORE_PAGE_SIZE, drainPages } from "./pagination.js";
4
+ export type { DrainPagesOptions, PagedResult } from "./pagination.js";
3
5
  export type { BackupGlobalSettings } from "./backup.js";
4
6
  export type { CostAllocationTag } from "./costAllocation.js";
5
7
  export type { CreateAccountResult } from "./accounts.js";
@@ -15,5 +17,7 @@ export { listAccounts, findAccount, createAccount } from "./accounts.js";
15
17
  export { ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap } from "./organisationalUnits.js";
16
18
  export { activateCostAllocationTags } from "./costAllocation.js";
17
19
  export { checkIdentityCentreStatus } from "./identityCentre.js";
20
+ export { snapshotIdentityCentre } from "./identityCentreSnapshot.js";
21
+ export type { IdentityCentreSnapshot, IdentityCentreSnapshotGroup, IdentityCentreSnapshotOptions, IdentityCentreSnapshotResult, IdentityCentreSnapshotUser } from "./identityCentreSnapshot.js";
18
22
  export { registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS } from "./delegatedAdmin.js";
19
23
  export { enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./rootAccess.js";
@@ -1 +1 @@
1
- import{extractErrorName as r,isOULeaf as o}from"./types.js";import{ensureOrganisationExists as s,describeOrganisation as c}from"./organisation.js";import{enablePolicyTypes as i}from"./policies.js";import{enableServiceAccess as m,SERVICE_PRINCIPALS as l}from"./serviceAccess.js";import{enableRamSharing as f}from"./ram.js";import{activateTrustedAccess as u}from"./trustedAccess.js";import{enableIpamDelegatedAdmin as S}from"./ipam.js";import{updateBackupGlobalSettings as I}from"./backup.js";import{listAccounts as b,findAccount as d,createAccount as g}from"./accounts.js";import{ensureOrganisationalUnitsExist as T,placeAccountsInOUs as U,buildAccountToOUMap as P}from"./organisationalUnits.js";import{activateCostAllocationTags as y}from"./costAllocation.js";import{checkIdentityCentreStatus as L}from"./identityCentre.js";import{registerSecurityDelegates as h,SECURITY_SERVICE_PRINCIPALS as k}from"./delegatedAdmin.js";import{enableCentralisedRootAccess as V,ROOT_ACCESS_FEATURES as B,RootAccessEnablementError as F}from"./rootAccess.js";export{B as ROOT_ACCESS_FEATURES,F as RootAccessEnablementError,k as SECURITY_SERVICE_PRINCIPALS,l as SERVICE_PRINCIPALS,y as activateCostAllocationTags,u as activateTrustedAccess,P as buildAccountToOUMap,L as checkIdentityCentreStatus,g as createAccount,c as describeOrganisation,V as enableCentralisedRootAccess,S as enableIpamDelegatedAdmin,i as enablePolicyTypes,f as enableRamSharing,m as enableServiceAccess,s as ensureOrganisationExists,T as ensureOrganisationalUnitsExist,r as extractErrorName,d as findAccount,o as isOULeaf,b as listAccounts,U as placeAccountsInOUs,h as registerSecurityDelegates,I as updateBackupGlobalSettings};
1
+ import{composeSdkAbortSignal as r,extractErrorName as o,isAborted as a,isOULeaf as s}from"./types.js";import{DEFAULT_MAX_PAGES as c,IDENTITY_STORE_PAGE_SIZE as i,drainPages as p}from"./pagination.js";import{ensureOrganisationExists as A,describeOrganisation as l}from"./organisation.js";import{enablePolicyTypes as E}from"./policies.js";import{enableServiceAccess as S,SERVICE_PRINCIPALS as I}from"./serviceAccess.js";import{enableRamSharing as u}from"./ram.js";import{activateTrustedAccess as g}from"./trustedAccess.js";import{enableIpamDelegatedAdmin as R}from"./ipam.js";import{updateBackupGlobalSettings as _}from"./backup.js";import{listAccounts as P,findAccount as U,createAccount as y}from"./accounts.js";import{ensureOrganisationalUnitsExist as L,placeAccountsInOUs as N,buildAccountToOUMap as h}from"./organisationalUnits.js";import{activateCostAllocationTags as v}from"./costAllocation.js";import{checkIdentityCentreStatus as F}from"./identityCentre.js";import{snapshotIdentityCentre as V}from"./identityCentreSnapshot.js";import{registerSecurityDelegates as B,SECURITY_SERVICE_PRINCIPALS as X}from"./delegatedAdmin.js";import{enableCentralisedRootAccess as j,ROOT_ACCESS_FEATURES as q,RootAccessEnablementError as w}from"./rootAccess.js";export{c as DEFAULT_MAX_PAGES,i as IDENTITY_STORE_PAGE_SIZE,q as ROOT_ACCESS_FEATURES,w as RootAccessEnablementError,X as SECURITY_SERVICE_PRINCIPALS,I as SERVICE_PRINCIPALS,v as activateCostAllocationTags,g as activateTrustedAccess,h as buildAccountToOUMap,F as checkIdentityCentreStatus,r as composeSdkAbortSignal,y as createAccount,l as describeOrganisation,p as drainPages,j as enableCentralisedRootAccess,R as enableIpamDelegatedAdmin,E as enablePolicyTypes,u as enableRamSharing,S as enableServiceAccess,A as ensureOrganisationExists,L as ensureOrganisationalUnitsExist,o as extractErrorName,U as findAccount,a as isAborted,s as isOULeaf,P as listAccounts,N as placeAccountsInOUs,B as registerSecurityDelegates,V as snapshotIdentityCentre,_ as updateBackupGlobalSettings};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Canonical pagination helpers for identity-store style AWS list APIs.
3
+ * Shared by the Identity Centre snapshot (deploy-core) and the CLI
4
+ * identity-store lister — the page size and page budget are coupled
5
+ * values that must move together, so both consumers import from here.
6
+ */
7
+ export declare const DEFAULT_MAX_PAGES = 20;
8
+ /** MaxResults sent on every identity-store list call (~1000 items under the default page budget). */
9
+ export declare const IDENTITY_STORE_PAGE_SIZE = 50;
10
+ export interface PagedResult<T> {
11
+ items: T[];
12
+ /** True when the abort signal fired or the page budget ran out. */
13
+ truncated: boolean;
14
+ }
15
+ export interface DrainPagesOptions {
16
+ abortSignal?: AbortSignal;
17
+ /** Page budget per listing call. Default {@link DEFAULT_MAX_PAGES}. */
18
+ maxPages?: number;
19
+ }
20
+ /**
21
+ * Drain a paginated listing under a page budget with a per-page abort
22
+ * pre-check. Truncation (budget exhausted or abort) is reported, never
23
+ * silent.
24
+ */
25
+ export declare function drainPages<T>(fetchPage: (nextToken: string | undefined) => Promise<{
26
+ items: T[];
27
+ nextToken: string | undefined;
28
+ }>, options?: DrainPagesOptions): Promise<PagedResult<T>>;
@@ -0,0 +1 @@
1
+ import{isAborted as i}from"./types.js";const c=20,d=50;async function x(o,n={}){const s=n.maxPages??c,e=[];let t,a=0;do{if(i(n.abortSignal)||a>=s)return{items:e,truncated:!0};const r=await o(t);e.push(...r.items),t=r.nextToken,a+=1}while(t!==void 0);return{items:e,truncated:!1}}export{c as DEFAULT_MAX_PAGES,d as IDENTITY_STORE_PAGE_SIZE,x as drainPages};
@@ -15,15 +15,15 @@ export { DeploymentEventSchema, DEPLOYMENT_EVENT_TYPES, DEPLOYMENT_EVENT_RESOURC
15
15
  export type { DeploymentEvent, DeploymentEventType, DeploymentEventResourceCategory, DeploymentEventCascadePhase, DeploymentEventCascadeAccountStatus } from "./types/index.js";
16
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
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, ScopedBuildSecretSessionMinter, 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";
18
+ export type { AwsCredentials, DeployIdentity, ScopedBuildSecretSessionMinter, 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, ApprovalGatePlanDetail, ApprovalRequest, GateResolution, OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession, Entitlements } from "./types/index.js";
19
19
  export type { AwsProvider, AwsProviderCredentials, AwsSdkClientConstructor } from "./aws/index.js";
20
20
  export { SimpleAwsProvider } from "./aws/index.js";
21
21
  export { checkTargetReadiness, describeTargetReadinessReason, buildTargetUnreadyAdvisory, buildTargetSetUnreadyAdvisory, probeAccountStackExists } from "./aws/index.js";
22
22
  export type { TargetReadinessAccount, TargetReadinessClients, TargetReadinessReason, TargetReadinessVerdict, TargetSetUnreadyAdvisoryParams } from "./aws/index.js";
23
23
  export { checkRegionEnabled, regionDisabledMessage } from "./aws/index.js";
24
24
  export type { RegionAvailability } from "./aws/index.js";
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";
26
- export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./aws/index.js";
25
+ export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, snapshotIdentityCentre, composeSdkAbortSignal, extractErrorName, isAborted, DEFAULT_MAX_PAGES, IDENTITY_STORE_PAGE_SIZE, drainPages, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./aws/index.js";
26
+ export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, DrainPagesOptions, PagedResult, IdentityCentreStatus, IdentityCentreSnapshot, IdentityCentreSnapshotGroup, IdentityCentreSnapshotOptions, IdentityCentreSnapshotResult, IdentityCentreSnapshotUser, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./aws/index.js";
27
27
  export { STEP_IDS, STEP_NAMES, INFRASTRUCTURE_STEP_NAMES, INFRA_STEP_NAME } from "./types/index.js";
28
28
  export type { StepId, InfrastructureStepName, StepDefinition, StepDeploymentType, Operation, StepContext } from "./types/index.js";
29
29
  export type { DeploymentContext, StepOutput, ApplicationDeploymentContext, CallerIdentity, StackOutput, StackOutputsRecord, ValidationResult, DetectionResult, ProgressCallbacks, ApplicationStack, OrganisationType, DeploymentOperation, ApplicationOperation, OrganisationOperation, DeployOrderOptions, ParallelDeployGroupKey, ParallelDestroyGroupKey, OpenNextPattern, AppResourceFlags, ParallelDeploymentResult, ParallelDeployGroup, ParallelOperation, ParallelOperationType, ParallelOperationState, ApplicationErrorType, StackDeploymentData, StackDeploymentOptions, ApplicationDeploymentData, ApplicationDestructionData, FjallStateFile, TemplateHashEntry } from "./types/index.js";
@@ -35,7 +35,9 @@ export type { CdkContext, CdkOptions, CdkOutput, CheckDifferencesResult, CdkServ
35
35
  export { CdkError } from "./types/errors/index.js";
36
36
  export { type ServiceErrorDetails, type ServiceError, BaseServiceError, ValidationError, AuthError, AwsError, DeploymentError, NetworkError, FileSystemError, ConfigError, toServiceError } from "./types/errors/index.js";
37
37
  export { filterDangerousEnvVars, maskSensitiveOutput, parseShellArgs, sleep } from "@fjall/util";
38
+ export { artefactOutputKey, DeployModeSchema, ServiceArtefactSchema, ServiceArtefactsSchema, type ArtefactOutputField, type DeployMode, type ServiceArtefact, type ServiceArtefacts } from "@fjall/util";
38
39
  export { hasDockerfile } from "./util/dockerfileDetection.js";
40
+ export { sleepAbortable } from "./util/sleepAbortable.js";
39
41
  export { createSequencedCallbacks } from "./util/sequencedCallbacks.js";
40
42
  export type { CallbackMetadata, SequencedCallbackEvent, SequencedCallbacksResult } from "./util/sequencedCallbacks.js";
41
43
  export { fileExists } from "@fjall/util/fsHelpers";
@@ -64,8 +66,8 @@ export type { AccountsConfig } from "./orchestration/index.js";
64
66
  export { runOpenNextBuild } from "./orchestration/index.js";
65
67
  export { runOrganisationSetup, ORG_SETUP_PHASES } from "./orchestration/index.js";
66
68
  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";
69
+ export { resolveBuildSecrets, resolveSecretRefValue, resolveBuildSecretSessionProvider, sourcedRefsFromBuildArgs, resolveBuildArgs, buildBuildSecretSessionPolicy, buildDeploySessionPolicy, partitionForRegion, validateBuildGroup } from "./orchestration/index.js";
70
+ export type { ResolveBuildArgsContext, ResolveBuildArgsResult, SourcedBuildArgResolver, BuildSecretRef, BuildSecretSessionContext, DeploySessionContext, ValidateBuildGroupInput, BuildGroupMemberInputs } from "./orchestration/index.js";
69
71
  export type { FrameworkBuilder, FrameworkDetection, BuildPlan, BuildCommand, BuildCallbacks, DetectionContext, BuildOptions } from "./types/index.js";
70
72
  export { FrameworkRegistry, type ResolvedBuilder } from "./orchestration/index.js";
71
73
  export { openNextBuilder, dockerBuilder } 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 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
+ 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 d,DEPLOY_EVENT_NAME_MAX as C,DEPLOY_EVENT_MESSAGE_MAX as N,DEPLOY_EVENT_STATUS_REASON_MAX as D,DEPLOY_EVENT_ERROR_MESSAGE_MAX as L,DEPLOY_EVENT_TRAIL_DETAIL_MAX as f,DEPLOY_EVENT_OUTPUT_MAX as g,DEPLOY_EVENT_ARN_MAX as I,DEPLOY_EVENT_URL_MAX as y,STEP_COMPLETE_STATUSES as x,LOG_LEVELS as U,STACK_CLEANUP_PHASES as M,CASCADE_ACCOUNT_PHASES as v,DEPLOY_RESULTS as k,DETECTION_PHASES as Y,PHASE_STATUSES as h,BUILDERS as b,CASCADE_OUTCOME_RESULTS as V,PLAN_ACTIONS as F,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 de,snapshotIdentityCentre as Ce,composeSdkAbortSignal as Ne,extractErrorName as De,isAborted as Le,DEFAULT_MAX_PAGES as fe,IDENTITY_STORE_PAGE_SIZE as ge,drainPages as Ie,isOULeaf as ye,registerSecurityDelegates as xe,SECURITY_SERVICE_PRINCIPALS as Ue,enableCentralisedRootAccess as Me,ROOT_ACCESS_FEATURES as ve,RootAccessEnablementError as ke}from"./aws/index.js";import{STEP_IDS as he,STEP_NAMES as be,INFRASTRUCTURE_STEP_NAMES as Ve,INFRA_STEP_NAME as Fe}from"./types/index.js";import{ProgressReporter as Ge,APPLICATION_STACKS as Ke,ORGANISATION_TYPES as Xe,APPLICATION_DEPLOY_ORDER as He,APPLICATION_DESTROY_ORDER as we,OPENNEXT_DEPLOY_ORDER as je,OPENNEXT_DESTROY_ORDER as Qe,PARALLEL_DEPLOY_GROUPS as qe,PARALLEL_DESTROY_GROUPS as Je,OPENNEXT_PARALLEL_GROUPS as We,PARALLEL_OPERATION_TYPES as Ze,isApplicationOperation as ze,isOrganisationOperation as $e,getParallelDeployGroups as er,getParallelDestroyGroups as rr,getApplicationDeployOrder as tr,getApplicationDestroyOrder as or,getApplicationStackName as Er,getOrganisationStackName as ar,isApplicationStack as ir,isQuarantineDetail as sr,isRetainedBucketsDetail as Sr,getApplicationStepName as nr,getApplicationStepId as cr,toPascalCase as Ar,isOpenNextPattern as Tr,OPENNEXT_PATTERNS as _r,deriveResourcesFromManifestStacks as lr,STACK_NOT_FOUND_PATTERN as pr,STACK_FAILED_STATE_PATTERN as Or,CDK_NO_STACKS_MATCH as Pr,INFRASTRUCTURE_FILENAME as Rr,ApplicationError as ur,wrapApplicationError as mr,FjallStateFileSchema as dr,readStateFile as Cr,writeStateFile as Nr,createEmptyState as Dr,deleteStateFile as Lr,updateTemplateHash as fr,getStateFilePath as gr,stubCallerIdentity as Ir}from"./types/index.js";import{CloudFormationEventMonitor as xr}from"./aws/index.js";import{CdkService as Mr,CdkArgumentBuilder as vr,CdkProcessManager as kr,CdkEventMonitor as Yr,startStackMonitoring as hr,DEFAULT_DEPLOY_TIMEOUT_MS as br,isCdkError as Vr,formatInfrastructureError as Fr,getStructuralHint as Br,getSourceContext as Gr,hasCdkDifferences as Kr,parseDiffOutput as Xr,CloudFormationService as Hr,CloudFormationError as wr,EcsService as jr,EcsError as Qr,EcsServiceResolver as qr,TemplateHashService as Jr,TemplateHashError as Wr,CdkContextBuilder as Zr,emitProgress as zr,PROGRESS_MESSAGES as $r,parseBuildPhase as et,buildStepContextBuildConfig as rt,convertCloudFormationOutputsToRecord as tt,ApplicationStackService as ot}from"./services/index.js";import{CdkError as at}from"./types/errors/index.js";import{BaseServiceError as st,ValidationError as St,AuthError as nt,AwsError as ct,DeploymentError as At,NetworkError as Tt,FileSystemError as _t,ConfigError as lt,toServiceError as pt}from"./types/errors/index.js";import{filterDangerousEnvVars as Pt,maskSensitiveOutput as Rt,parseShellArgs as ut,sleep as mt}from"@fjall/util";import{artefactOutputKey as Ct,DeployModeSchema as Nt,ServiceArtefactSchema as Dt,ServiceArtefactsSchema as Lt}from"@fjall/util";import{hasDockerfile as gt}from"./util/dockerfileDetection.js";import{sleepAbortable as yt}from"./util/sleepAbortable.js";import{createSequencedCallbacks as Ut}from"./util/sequencedCallbacks.js";import{fileExists as vt}from"@fjall/util/fsHelpers";import{success as Yt,failure as ht,isSuccess as bt,isFailure as Vt}from"@fjall/generator";import{deploy as Bt}from"./orchestration/index.js";import{destroy as Kt}from"./orchestration/index.js";import{partitionAccounts as Ht}from"./orchestration/index.js";import{buildRegionList as jt,buildAccountRegionPairs as Qt,cascadeHomeRegion as qt,cascadeOperationKey as Jt}from"./orchestration/index.js";import{projectScalarSummary as Zt,projectAccountRows as zt}from"./orchestration/index.js";import{reconcileProviderAccounts as eo,mergeReconciledProviderAccounts as ro}from"./orchestration/index.js";import{decideNextTransition as oo,reconcileTrailMigration as Eo,decommissionMemberTrailStorage as ao,ORG_TRAIL_BUCKET_OUTPUT_KEY as io,TRAIL_BUCKET_OUTPUT_KEY as so,TRAIL_KEY_ARN_OUTPUT_KEY as So}from"./orchestration/index.js";import{unlockBucket as co,unlockQueue as Ao}from"./orchestration/index.js";import{triageBucketPolicy as _o,isEnforceSslStatement as lo,toResourcePolicyStatements as po,restoreBucketPolicy as Oo,synthesiseEnforceSslDocument as Po,ensureEnforceSsl as Ro,restoreAndReconcileQuarantinedBucket as uo}from"./orchestration/index.js";import{verifyOrgTrailDelivery as Co}from"./aws/cloudtrail/orgTrailDelivery.js";import{assumeRootForTask as Do,isRootTaskPolicyArn as Lo,ROOT_TASK_POLICY_ARNS as fo,MAX_ROOT_SESSION_SECONDS as go}from"./aws/sts/assumeRoot.js";import{parseAccountsConfiguration as yo,flattenAccountsToEnvironments as xo,extractAllAccountNames as Uo,accountsConfigToOUTree as Mo,isStringArray as vo,isAccountsConfig as ko,isOuOnlyAccountBucket as Yo,OU_ONLY_ACCOUNT_BUCKETS as ho}from"./orchestration/index.js";import{runOpenNextBuild as Vo}from"./orchestration/index.js";import{runOrganisationSetup as Bo,ORG_SETUP_PHASES as Go}from"./orchestration/index.js";import{resolveBuildSecrets as Xo,resolveSecretRefValue as Ho,resolveBuildSecretSessionProvider as wo,sourcedRefsFromBuildArgs as jo,resolveBuildArgs as Qo,buildBuildSecretSessionPolicy as qo,buildDeploySessionPolicy as Jo,partitionForRegion as Wo,validateBuildGroup as Zo}from"./orchestration/index.js";import{FrameworkRegistry as $o}from"./orchestration/index.js";import{openNextBuilder as rE,dockerBuilder as tE}from"./orchestration/index.js";import{StepRegistry as EE,getDestroyStepId as aE}from"./steps/index.js";import{buildDeployPlan as sE,computeDeployPlan as SE,computeAssemblyDigest as nE,digestHead as cE,classifyImpact as AE,derivePropertyChanges as TE,isDataLoss as _E,isStatefulResourceType as lE,renderPlanSummary as pE,renderPlanLines as OE,toWirePlanChanges as PE,signApprovalToken as RE,verifyApprovalToken as uE,DEFAULT_APPROVAL_TTL_MS as mE,APPROVAL_TOKEN_PATTERN as dE,runApprovalGate as CE}from"./orchestration/index.js";export{X as ALL_PROGRESS_KINDS,He as APPLICATION_DEPLOY_ORDER,we as APPLICATION_DESTROY_ORDER,Ke as APPLICATION_STACKS,K as APPROVAL_DECISIONS,G as APPROVAL_KINDS,dE as APPROVAL_TOKEN_PATTERN,ur as ApplicationError,ot as ApplicationStackService,nt as AuthError,ct as AwsError,b as BUILDERS,st as BaseServiceError,v as CASCADE_ACCOUNT_PHASES,n as CASCADE_ACCOUNT_STATUSES,V as CASCADE_OUTCOME_RESULTS,S as CASCADE_PHASES,Pr as CDK_NO_STACKS_MATCH,vr as CdkArgumentBuilder,Zr as CdkContextBuilder,at as CdkError,Yr as CdkEventMonitor,kr as CdkProcessManager,Mr as CdkService,O as ChangeCountsSchema,wr as CloudFormationError,xr as CloudFormationEventMonitor,Hr as CloudFormationService,lt as ConfigError,mE as DEFAULT_APPROVAL_TTL_MS,br as DEFAULT_DEPLOY_TIMEOUT_MS,fe as DEFAULT_MAX_PAGES,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,d as DEPLOY_EVENT_ID_MAX,N as DEPLOY_EVENT_MESSAGE_MAX,C as DEPLOY_EVENT_NAME_MAX,g as DEPLOY_EVENT_OUTPUT_MAX,D as DEPLOY_EVENT_STATUS_REASON_MAX,f as DEPLOY_EVENT_TRAIL_DETAIL_MAX,y as DEPLOY_EVENT_URL_MAX,k as DEPLOY_RESULTS,Y as DETECTION_PHASES,A as DeployEventSchema,Nt as DeployModeSchema,_ as DeployResourceSchema,At as DeploymentError,t as DeploymentEventSchema,Qr as EcsError,jr as EcsService,qr as EcsServiceResolver,_t as FileSystemError,dr as FjallStateFileSchema,$o as FrameworkRegistry,ge as IDENTITY_STORE_PAGE_SIZE,Rr as INFRASTRUCTURE_FILENAME,Ve as INFRASTRUCTURE_STEP_NAMES,Fe as INFRA_STEP_NAME,U as LOG_LEVELS,go as MAX_ROOT_SESSION_SECONDS,Tt as NetworkError,je as OPENNEXT_DEPLOY_ORDER,Qe as OPENNEXT_DESTROY_ORDER,We as OPENNEXT_PARALLEL_GROUPS,_r as OPENNEXT_PATTERNS,Xe as ORGANISATION_TYPES,Go as ORG_SETUP_PHASES,io as ORG_TRAIL_BUCKET_OUTPUT_KEY,ho as OU_ONLY_ACCOUNT_BUCKETS,qe as PARALLEL_DEPLOY_GROUPS,Je as PARALLEL_DESTROY_GROUPS,Ze as PARALLEL_OPERATION_TYPES,h as PHASE_STATUSES,F as PLAN_ACTIONS,$r as PROGRESS_MESSAGES,l as PlanChangeSchema,p as PlanPropertyChangeSchema,Ge as ProgressReporter,B as REPLACEMENT_MODES,ve as ROOT_ACCESS_FEATURES,fo as ROOT_TASK_POLICY_ARNS,ke as RootAccessEnablementError,Ue as SECURITY_SERVICE_PRINCIPALS,ne as SERVICE_PRINCIPALS,M as STACK_CLEANUP_PHASES,Or as STACK_FAILED_STATE_PATTERN,pr as STACK_NOT_FOUND_PATTERN,x as STEP_COMPLETE_STATUSES,he as STEP_IDS,be as STEP_NAMES,m as SUPPORTED_CONTRACT_MARKERS,Dt as ServiceArtefactSchema,Lt as ServiceArtefactsSchema,q as SimpleAwsProvider,T as StepProgressSchema,EE as StepRegistry,so as TRAIL_BUCKET_OUTPUT_KEY,So as TRAIL_KEY_ARN_OUTPUT_KEY,Wr as TemplateHashError,Jr as TemplateHashService,St as ValidationError,Mo as accountsConfigToOUTree,me as activateCostAllocationTags,Ae as activateTrustedAccess,Ct as artefactOutputKey,Do as assumeRootForTask,Qt as buildAccountRegionPairs,ue as buildAccountToOUMap,qo as buildBuildSecretSessionPolicy,sE as buildDeployPlan,Jo as buildDeploySessionPolicy,jt as buildRegionList,rt as buildStepContextBuildConfig,$ as buildTargetSetUnreadyAdvisory,z as buildTargetUnreadyAdvisory,qt as cascadeHomeRegion,Jt as cascadeOperationKey,de as checkIdentityCentreStatus,te as checkRegionEnabled,W as checkTargetReadiness,AE as classifyImpact,Ne as composeSdkAbortSignal,nE as computeAssemblyDigest,SE as computeDeployPlan,tt as convertCloudFormationOutputsToRecord,Oe as createAccount,H as createDeployEmitter,Dr as createEmptyState,Ut as createSequencedCallbacks,oo as decideNextTransition,ao as decommissionMemberTrailStorage,Lr as deleteStateFile,Bt as deploy,TE as derivePropertyChanges,lr as deriveResourcesFromManifestStacks,ie as describeOrganisation,Z as describeTargetReadinessReason,Kt as destroy,cE as digestHead,tE as dockerBuilder,Ie as drainPages,zr as emitProgress,Me as enableCentralisedRootAccess,Te as enableIpamDelegatedAdmin,se as enablePolicyTypes,ce as enableRamSharing,Se as enableServiceAccess,Ro as ensureEnforceSsl,ae as ensureOrganisationExists,Pe as ensureOrganisationalUnitsExist,Uo as extractAllAccountNames,De as extractErrorName,ht as failure,vt as fileExists,Pt as filterDangerousEnvVars,pe as findAccount,xo as flattenAccountsToEnvironments,Fr as formatInfrastructureError,tr as getApplicationDeployOrder,or as getApplicationDestroyOrder,Er as getApplicationStackName,cr as getApplicationStepId,nr as getApplicationStepName,aE as getDestroyStepId,ar as getOrganisationStackName,er as getParallelDeployGroups,rr as getParallelDestroyGroups,Gr as getSourceContext,gr as getStateFilePath,Br as getStructuralHint,Kr as hasCdkDifferences,gt as hasDockerfile,Le as isAborted,ko as isAccountsConfig,ze as isApplicationOperation,ir as isApplicationStack,Vr as isCdkError,_E as isDataLoss,lo as isEnforceSslStatement,Vt as isFailure,ye as isOULeaf,Tr as isOpenNextPattern,$e as isOrganisationOperation,Yo as isOuOnlyAccountBucket,sr as isQuarantineDetail,Sr as isRetainedBucketsDetail,Lo as isRootTaskPolicyArn,lE as isStatefulResourceType,vo as isStringArray,bt as isSuccess,le as listAccounts,Rt as maskSensitiveOutput,ro as mergeReconciledProviderAccounts,rE as openNextBuilder,yo as parseAccountsConfiguration,et as parseBuildPhase,j as parseDeployContractVersion,w as parseDeployEvent,Xr as parseDiffOutput,ut as parseShellArgs,Ht as partitionAccounts,Wo as partitionForRegion,Re as placeAccountsInOUs,ee as probeAccountStackExists,zt as projectAccountRows,Zt as projectScalarSummary,Cr as readStateFile,eo as reconcileProviderAccounts,Eo as reconcileTrailMigration,oe as regionDisabledMessage,xe as registerSecurityDelegates,OE as renderPlanLines,pE as renderPlanSummary,Qo as resolveBuildArgs,wo as resolveBuildSecretSessionProvider,Xo as resolveBuildSecrets,Ho as resolveSecretRefValue,uo as restoreAndReconcileQuarantinedBucket,Oo as restoreBucketPolicy,CE as runApprovalGate,Vo as runOpenNextBuild,Bo as runOrganisationSetup,RE as signApprovalToken,mt as sleep,yt as sleepAbortable,Ce as snapshotIdentityCentre,jo as sourcedRefsFromBuildArgs,hr as startStackMonitoring,Ir as stubCallerIdentity,Yt as success,Po as synthesiseEnforceSslDocument,Ar as toPascalCase,po as toResourcePolicyStatements,pt as toServiceError,PE as toWirePlanChanges,_o as triageBucketPolicy,co as unlockBucket,Ao as unlockQueue,_e as updateBackupGlobalSettings,fr as updateTemplateHash,Zo as validateBuildGroup,uE as verifyApprovalToken,Co as verifyOrgTrailDelivery,mr as wrapApplicationError,Nr as writeStateFile};
@@ -1 +1 @@
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
+ import{join as $}from"path";import{CloudFormationClient as V}from"@aws-sdk/client-cloudformation";import{EC2Client as X}from"@aws-sdk/client-ec2";import{success as P,failure as g}from"@fjall/generator";import{logger as T}from"@fjall/util/logger";import{imageTagParameterName as Z,maskSensitiveOutput as y}from"@fjall/util";import{parseDockerServicesFromManifest as ee}from"@fjall/util/manifest";import{stubCallerIdentity as te}from"../../types/deployment/index.js";import{getApplicationDeployOrder as oe,getApplicationStackName as re,getApplicationStepName as W,getApplicationStepId as q,APPLICATION_STACKS as O}from"../../types/operations.js";import{CdkContextBuilder as ne}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as ae,bootstrapOrFail as ie}from"../contextHelpers.js";import{runDetectionPipeline as se}from"./detectionPipeline.js";import{STEP_IDS as N,STEP_NAMES as ce}from"../../types/stepDefinitions.js";import{StepRegistry as le}from"../../steps/stepRegistry.js";import{checkTargetReadiness as de}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as pe}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as ue}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as z,runDockerBuild as fe}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as ge,deployParallelPhase as me,deployStackSequential as ke,runDockerPreCompute as ye,createBuildCallbacks as he}from"./applicationDeployHelpers.js";import{deployCodeOnly as Se}from"./codeOnlyDeploy.js";import{collectFullDeployArtefacts as be}from"./artefactCollection.js";import{runApprovalGate as De}from"./approvalGate.js";const R="applicationDeploy";function we(i){const s=Object.entries(i);if(s.length===0)return;const n={};for(const[t,a]of s){const h=Z(t);n[h]=a}return n}async function qe(i,s,n){const{callbacks:t,options:a}=i,h=Date.now(),A=a?.mode??(a?.imageTag?"rollback":a?.deployOnly?"code-only":"full");if(a?.mode!==void 0&&a.mode!=="rollback"&&a.imageTag){const e=new Error(`--image-tag requires rollback mode (explicit mode '${a.mode}' was supplied alongside an image tag)`);return t.onError?.(e),g(e)}if(a?.mode==="rollback"&&!a.imageTag){const e=new Error("rollback mode requires --image-tag <tag> naming the image to roll to");return t.onError?.(e),g(e)}const w=ne.buildDeploymentContext({deployType:"application",target:n.appName,path:n.path,region:s.awsProvider.getRegion(),callerIdentity:te(s.awsProvider.getAccountId()),...ae({orgConfig:i.orgConfig,identity:i.identity,skipOidc:i.options?.skipOidc})},{verbose:a?.verbose,infraOnly:a?.infraOnly},i.orgConfig),p=s.frameworkRegistry.resolve({appPath:n.path});let C;const b=A==="rollback";if(p&&!b){C=p.builder.plan({appPath:n.path},p.detection);const e=he(t),r=await p.builder.build(n.path,C,e,{skipBuild:a?.skipBuild,infraOnly:a?.infraOnly});if(!r.success){const o=new Error(y(r.error.message));return t.onError?.(o),g(o)}}if(A==="code-only"||b){t.onLog?.(b?`Rollback mode \u2014 rolling to image tag ${a?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const e=$(n.path,"cdk.out"),r=ee(e),o=r.length>0,d=p?.detection.hasDockerfile===!0,l=o||d;T.debug(R,"Deploy-only branch entered",{mode:A,imageTag:a?.imageTag,appName:n.appName,appPath:n.path,cdkOutPath:e,dockerProviderAvailable:i.dockerProvider!==void 0,builderName:p?.builder.name,hasDockerfileFromManifest:o,hasDockerfileFromDisk:d,hasDockerfile:l,manifestDockerServiceCount:r.length,manifestDockerPaths:r.map(k=>k.docker.path)}),!o&&!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(!b&&i.dockerProvider!==void 0&&l){T.debug(R,"Running Docker build before code-only deploy",{source:o?"manifest":"disk"});const k=await fe(i,s,n,t);if(!k.success)return g(k.error);u=k.data.contentHashTagsByService}else T.debug(R,"Skipping Docker build",{reason:b?"rollback":i.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return Se(i,s,n,u)}t.onLog?.("Analysing infrastructure\u2026","info");const v=await se(n,s,w,t);if(!v.success){const e=new Error(y(v.error.message));return t.onError?.(e),g(e)}const c=v.data;try{await t.onDetectionComplete?.({...c,builderName:p?.builder.name??"unknown"})}catch(e){const r=e instanceof Error?e.message:String(e),o=new Error(y(r));return t.onError?.(o),g(o)}const Y={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:a?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...p&&{builderName:p.builder.name}},M=le.getSteps(Y),L=M.findIndex(e=>e.id===N.TARGET_READINESS),_=await ue(t,{stepId:N.TARGET_READINESS,stepName:ce.TARGET_READINESS,...L>=0&&{stepIndex:L,totalSteps:M.length}},async()=>{if(a?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const e=s.awsProvider.getAccountId(),r=i.orgConfig?.providerAccounts.find(u=>u.id===e)?.name,o=s.awsProvider.getCredentials(),d=pe(i.orgConfig),l=await de({cloudFormation:s.awsProvider.getClient(V),ec2:new X({region:d,...o!==void 0&&{credentials:o}})},{id:e,...r!==void 0&&{name:r}},s.awsProvider.getRegion(),i.orgConfig,i.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(y(l.data.advisory))}:{kind:"error",error:new Error(y(l.error.message))}},i.abortSignal);if(!_.success)return _;const m=C?C.deployOrder:oe({pattern:c.pattern,resources:c.resources}),S=m.length,x=c.hasDockerfile&&i.dockerProvider!==void 0&&m.includes(O.COMPUTE)&&a?.infraOnly!==!0;if(!c.hasDifferences&&!a?.force&&!x){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(a?.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&&i.dockerProvider!==void 0&&m.includes(O.COMPUTE);for(let o=0;o<m.length;o++){const d=m[o];e&&d===O.COMPUTE&&(t.onStepStart?.(N.DOCKER_OPERATIONS,z),t.onStepComplete?.(N.DOCKER_OPERATIONS,z,"skipped"));const l=q(d,"deploy"),u=W(d,"deploy");t.onStepStart?.(l,u,o,S),t.onStepComplete?.(l,u,"skipped",o,S)}const r=await s.stackService.resolveWebsiteUrl(n.appName);return P({target:n.appName,deploymentType:"application",outputs:r?{websiteUrl:r}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-h})}if(i.approvalGate!==void 0||a?.approvalToken!==void 0&&a.approvalToken!==""||a?.planOnly===!0){const e=$(n.path,"cdk.out"),r=await De({approvalGate:i.approvalGate,options:a??{},target:n.appName,cdkOutPath:e,detection:{stackChanges:c.stackChanges,currentHashes:c.currentHashes},cfnService:s.cfnService,callbacks:t});if(!r.success)return r;const o=r.data;if(o.kind==="rejected")return t.onLog?.(`Deploy rejected: ${y(o.reason)}`,"warn"),P({target:n.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-h});if(o.kind==="awaiting")return P({target:n.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,artefacts:[],durationMs:Date.now()-h})}const B=await ie(s,w,t);if(!B.success)return B;const D={},E=new Map;let H;for(let e=0;e<m.length;e++){const r=m[e],o=re(n.appName,r),d=q(r,"deploy"),l=W(r,"deploy"),u=c.stackChanges.get(o)??!0,k=r===O.COMPUTE&&x;if(!u&&!a?.force&&!k){t.onStepStart?.(d,l,e,S),t.onLog?.(`Skipping ${r} \u2014 no changes detected`,"info"),t.onStepComplete?.(d,l,"skipped",e,S);continue}const I=ge(m,e,c.stackChanges,n.appName,a?.force);if(I.length>=2){const K=await me(I,n,s,w,t,e,S,c,D,E);if(!K.success)return g(K.error);e+=I.length-1;continue}const f=await ye(r,i,s,n,t,c.hasDockerfile);if(f!==null&&!f.success)return g(f.error);f!==null&&f.success&&(H=f.data);const Q=f!==null&&f.success?f.data.contentHashTagsByService:{},G=we(Q),j=await ke(r,s,w,t,e,S,D,G!==void 0?{parameters:G}:void 0);if(!j.success)return g(j.error);const F=c.currentHashes.get(o);F&&E.set(o,F)}if(E.size>0){const e=await s.hashService.updateStateAfterDeploy(n.path,E);if(!e.success){const r=y(e.error.message);T.debug(R,"Failed to update state file",{error:r}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${r}`,"warn")}}const U=await s.stackService.resolveWebsiteUrl(n.appName);U&&(D.websiteUrl=U);const J=await be(s,n.appName,H,t);return P({target:n.appName,deploymentType:"application",outputs:Object.keys(D).length>0?D:void 0,artefacts:J,durationMs:Date.now()-h})}export{qe as deployApplication};
@@ -5,6 +5,7 @@ import type { DeployCallbacks } from "../../types/callbacks.js";
5
5
  import type { ApplicationOperation, ApplicationStack } from "../../types/operations.js";
6
6
  import type { DeployServices } from "../serviceFactory.js";
7
7
  import type { DetectionResult } from "./detectionPipeline.js";
8
+ import { type DockerBuildProducts } from "./dockerBuildHelper.js";
8
9
  import type { BuildCallbacks } from "../../types/frameworkBuilder.js";
9
10
  /**
10
11
  * Identify Phase 2 stacks (Storage, Messaging, Database) that are consecutive
@@ -23,20 +24,18 @@ interface DeployStackSequentialOptions {
23
24
  * Deploy a single stack with step lifecycle events and output collection.
24
25
  */
25
26
  export declare function deployStackSequential(stack: ApplicationStack, services: DeployServices, context: DeploymentContext, callbacks: DeployCallbacks, stepIndex: number, totalSteps: number, allOutputs: Record<string, string>, deployOptions?: DeployStackSequentialOptions): Promise<Result<void>>;
26
- export interface DockerPreComputeOutcome {
27
- /**
28
- * Map from service name (with optional target suffix) to the resolved
29
- * content-hash tag pushed during this build phase. Empty for the legacy
30
- * single-image fallback path and for welcome-image setup; the orchestrator
31
- * skips `--parameters` plumbing in those cases.
32
- */
33
- contentHashTagsByService: Record<string, string>;
34
- }
27
+ /**
28
+ * What the pre-Compute docker phase produced: the per-service content-hash
29
+ * tag map (drives `--parameters` plumbing; empty for the legacy fallback and
30
+ * welcome-image paths) plus the digests/repo URI consumed by full-deploy
31
+ * artefact collection.
32
+ */
33
+ export type DockerPreComputeOutcome = DockerBuildProducts;
35
34
  /**
36
35
  * Run Docker build/push or welcome image setup before the Compute stack.
37
- * Returns a `Result<DockerPreComputeOutcome>` on success carrying the
38
- * per-service content-hash tag map, a failure on docker error, or `null`
39
- * when no docker work is required for this stack.
36
+ * Returns a `Result<DockerPreComputeOutcome>` on success carrying the build
37
+ * products, a failure on docker error, or `null` when no docker work is
38
+ * required for this stack.
40
39
  */
41
40
  export declare function runDockerPreCompute(stack: ApplicationStack, params: DeployParams, services: DeployServices, operation: ApplicationOperation, callbacks: DeployCallbacks, hasDockerfileOnDisk: boolean): Promise<Result<DockerPreComputeOutcome> | null>;
42
41
  /**
@@ -1,4 +1,4 @@
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(`
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 x,forwardResourceProgress as _}from"../contextHelpers.js";import{emptyBuildProducts as k,runDockerBuild as $}from"./dockerBuildHelper.js";import{runWelcomeImageSetup as j}from"./welcomeImageHelper.js";import{withStepLifecycle as D}from"../stepLifecycle.js";function W(t,o,s,p,r){const d=new Set(L.PHASE_2.stacks),i=[];for(let n=o;n<t.length&&d.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 Y(t,o,s,p,r,d,i,n,f,m){for(let e=0;e<t.length;e++){const u=t[e],l=N(u,"deploy"),c=y(u,"deploy");r.onStepStart?.(l,c,d+e,i)}I(r,"Deploying infrastructure in parallel\u2026"),r.onParallelPhaseStart?.(t,"Storage and database resources (parallel)");const a=await s.stackService.deployStacksInParallel(t,p,{onOutput:x(r),onResourceProgress:(e,u)=>{r.onResourceProgress?.(e),u&&r.onParallelStackResourceProgress?.(u,e)},onStackComplete:(e,u,l,c)=>{const P=N(e,"deploy"),A=y(e,"deploy"),B=d+t.indexOf(e),w=R(s.abortSignal);r.onStepComplete?.(P,A,u?"completed":w?"cancelled":"error",B,i),!u&&c&&!w&&r.onError?.(new Error(C(c.message)))}});if(!a.success)return r.onParallelPhaseComplete?.([]),S(a.error);const g=a.data.filter(e=>!e.success);if(r.onParallelPhaseComplete?.(a.data.map(e=>({stack:e.stack,success:e.success,error:e.error}))),g.length>0){const e=g.map(l=>l.stack).join(", "),u=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
- ${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};
4
+ ${u}`))}for(const e of a.data){if(e.success&&e.outputs)for(const[c,P]of Object.entries(e.outputs))f[c]=String(P);const u=O(o.appName,e.stack),l=n.currentHashes.get(u);l&&m.set(u,l)}return h(void 0)}async function z(t,o,s,p,r,d,i,n){const f=N(t,"deploy"),m=y(t,"deploy");return D(p,{stepId:f,stepName:m,stepIndex:r,totalSteps:d},async()=>{const a=await o.stackService.deployStack(t,s,{onOutput:x(p),onResourceProgress:_(p)},n?.parameters!==void 0?{parameters:n.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))i[g]=String(e);return{kind:"completed",data:void 0}},o.abortSignal)}async function J(t,o,s,p,r,d){if(t!==E.COMPUTE||!o.dockerProvider)return null;if(d){const n=await $(o,s,p,r);return n.success?h(n.data):S(n.error)}const i=await j(o,s,p,r);return i.success?h(k()):S(i.error)}function Q(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{Q as createBuildCallbacks,Y as deployParallelPhase,z as deployStackSequential,W as getParallelPhase2Stacks,J as runDockerPreCompute};
@@ -15,7 +15,7 @@
15
15
  import { type Result } from "@fjall/generator";
16
16
  import type { DeployCallbacks } from "../../types/callbacks.js";
17
17
  import type { DeployOptions } from "../../types/params.js";
18
- import type { ApprovalGate } from "../../types/approval.js";
18
+ import type { ApprovalGate, ApprovalGatePlanDetail } from "../../types/approval.js";
19
19
  import { type CfnTemplateReader } from "./plan/computeDeployPlan.js";
20
20
  export interface RunApprovalGateParams {
21
21
  /** The surface resolver. Optional — a token-resume or plan-only run needs none. */
@@ -35,10 +35,12 @@ export type ApprovalGateOutcome = {
35
35
  } | {
36
36
  kind: "rejected";
37
37
  reason: string;
38
+ planDetail?: ApprovalGatePlanDetail;
38
39
  } | {
39
40
  kind: "awaiting";
40
41
  approvalToken: string;
41
42
  assemblyDigest: string;
42
43
  expiresAt: string;
44
+ planDetail: ApprovalGatePlanDetail;
43
45
  };
44
46
  export declare function runApprovalGate(params: RunApprovalGateParams): Promise<Result<ApprovalGateOutcome, Error>>;
@@ -1 +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};
1
+ import{join as k}from"node:path";import{readFile as g}from"node:fs/promises";import{success as s,failure as d}from"@fjall/generator";import{maskSensitiveOutput as h}from"@fjall/util";import{computeAssemblyDigest as v}from"./plan/assemblyDigest.js";import{computeDeployPlan as m}from"./plan/computeDeployPlan.js";import{renderPlanLines as y}from"./plan/renderDeployPlan.js";import{signApprovalToken as w,verifyApprovalToken as A}from"./plan/approvalToken.js";function x(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 D(e){const t=[];for(const n of e.changedStacks){const i=k(e.cdkOutPath,`${n}.template.json`);let r;try{r=JSON.parse(await g(i,"utf8"))}catch(o){return d(new Error(h(`Failed to read synthesised template for ${n}: ${o instanceof Error?o.message:String(o)}`)))}t.push({stackName:n,newTemplate:r})}return m({cfnService:e.cfnService,stacks:t,assemblyDigest:e.assemblyDigest})}function S(e,t,n){const i=t.changeCount===0&&n.length>0?`no resource changes against the live stacks; template hashes differ from the last recorded deploy for ${n.length} stack(s) (${n.join(", ")}) \u2014 applying will redeploy them`:t.summary;e.onLog?.(`Deploy plan: ${i}`,"info");for(const r of y(t))e.onLog?.(r,"info")}async function G(e){const t=v(e.detection.currentHashes),n=e.options.approvalToken;if(n!==void 0&&n!==""){const a=A({token:n,assemblyDigest:t});if(a.ok)return e.callbacks.onLog?.("Approval token verified against the current plan \u2014 proceeding","info"),s({kind:"proceed"});const u=x(a.reason);return e.callbacks.onLog?.(`Approval refused: ${u}`,"warn"),s({kind:"rejected",reason:u})}const i=[...e.detection.stackChanges.entries()].filter(([,a])=>a).map(([a])=>a),r=await D({cfnService:e.cfnService,cdkOutPath:e.cdkOutPath,changedStacks:i,assemblyDigest:t});if(!r.success)return d(r.error);const o=r.data;S(e.callbacks,o,i);const p={hashDriftStacks:i,resourceChangeCount:o.changeCount,resourceChangedStacks:[...new Set(o.changes.map(a=>a.stack))]},c=w({assemblyDigest:t});if(e.options.autoApprove===!0)return e.callbacks.onLog?.("Plan auto-approved (--auto-approve)","info"),s({kind:"proceed"});if(e.options.planOnly===!0)return s({kind:"awaiting",approvalToken:c.token,assemblyDigest:t,expiresAt:c.expiresAt,planDetail:p});if(e.approvalGate===void 0)return s({kind:"awaiting",approvalToken:c.token,assemblyDigest:t,expiresAt:c.expiresAt,planDetail:p});const f={target:e.target,plan:o,approvalToken:c.token,expiresAt:c.expiresAt,hasDestructiveChanges:o.hasDestructiveChanges},l=await e.approvalGate.resolve(f);switch(l.kind){case"approved":return s({kind:"proceed"});case"rejected":return s({kind:"rejected",reason:l.reason,planDetail:p});case"suspended":return s({kind:"awaiting",approvalToken:l.approvalToken,assemblyDigest:t,expiresAt:c.expiresAt,planDetail:p})}}export{G as runApprovalGate};
@@ -0,0 +1,18 @@
1
+ import { type ServiceArtefact } from "@fjall/util";
2
+ import type { DeployCallbacks } from "../../types/callbacks.js";
3
+ import type { DeployServices } from "../serviceFactory.js";
4
+ import type { DockerBuildProducts } from "./dockerBuildHelper.js";
5
+ /**
6
+ * Collect `ServiceArtefact[]` for a successful FULL deploy from the build
7
+ * step's products plus a best-effort post-deploy ECS resolution (the same
8
+ * discovery `deployCodeOnly` uses). Full deploys roll images via CFN
9
+ * `<Service>ImageTag` parameters, so the live task definition is only
10
+ * knowable by describing the services AFTER the deploy.
11
+ *
12
+ * Best-effort by contract: a discovery or describe failure degrades the
13
+ * artefact (ECS-resolved `serviceName` falls back to the build's manifest
14
+ * service name; `taskDefinitionArn` stays absent) and warns — it NEVER fails
15
+ * a deploy that already succeeded. Returns `[]` when the deploy pushed no
16
+ * images (welcome-image seed, legacy fallback, infra-only).
17
+ */
18
+ export declare function collectFullDeployArtefacts(services: DeployServices, appName: string, build: DockerBuildProducts | undefined, callbacks: DeployCallbacks): Promise<ServiceArtefact[]>;
@@ -0,0 +1 @@
1
+ import{maskSensitiveOutput as S}from"@fjall/util";import{logger as g}from"@fjall/util/logger";import{extractEcsServiceName as l}from"../../aws/utils/arnParser.js";import{deriveEcrRepositoryArn as A}from"./codeOnlyDeploy.js";const m="artefactCollection";async function C(c,t,r,n){if(r===void 0||r.repoUri===void 0)return[];const s=Object.entries(r.contentHashTagsByService);if(s.length===0)return[];const o=r.repoUri,i=A(o),d=await y(c,t,n);return s.map(([e,a])=>{const v=r.imageDigestsByService[e],f=p(e,d);return{serviceName:f?.ecsServiceName??e,imageTag:a,imageUri:`${o}:${a}`,...v!==void 0?{imageDigest:v}:{},...i!==""?{ecrRepositoryArn:i}:{},...f?.taskDefinitionArn!==void 0?{taskDefinitionArn:f.taskDefinitionArn}:{}}})}async function y(c,t,r){try{const n=await c.ecsResolver.getDeployableClusterAndServices(t);if(!n.success)return u(r,t,n.error.message),[];const{clusterArn:s,serviceArns:o}=n.data;if(!s||o.length===0)return g.debug(m,"No deployable ECS services discovered",{appName:t}),[];const i=await c.ecsService.describeServices(s,o);if(!i.success)return u(r,t,i.error.message),o.map(e=>l(e)).filter(e=>e!==void 0&&e!=="").map(e=>({ecsServiceName:e}));const d=[];for(const e of i.data){const a=e.serviceName??(e.serviceArn!==void 0?l(e.serviceArn):void 0);a===void 0||a===""||d.push({ecsServiceName:a,...e.taskDefinition!==void 0?{taskDefinitionArn:e.taskDefinition}:{}})}return d}catch(n){return u(r,t,n instanceof Error?n.message:String(n)),[]}}function p(c,t){const r=c.toLowerCase(),n=t.find(i=>i.ecsServiceName.toLowerCase()===r);if(n)return n;const s=t.filter(i=>i.ecsServiceName.toLowerCase().includes(r));if(s.length===1)return s[0];const o=s.filter(i=>i.ecsServiceName.toLowerCase().endsWith(`-${r}`));if(o.length===1)return o[0]}function u(c,t,r){const n=S(r);g.debug(m,"Post-deploy artefact resolution degraded",{appName:t,error:n}),c.onLog?.(`Deploy succeeded, but resolving live task definitions for release artefacts failed: ${n}. Artefacts are recorded without task definition ARNs.`,"warn")}export{C as collectFullDeployArtefacts};
@@ -16,6 +16,14 @@ export interface BuildSecretSessionContext {
16
16
  accountId: string;
17
17
  partition: string;
18
18
  }
19
+ /**
20
+ * The inline session policy must stay well under the AWS hard limit (2048 chars
21
+ * for an AssumeRoleWithWebIdentity inline `Policy`). We fail closed before
22
+ * minting a session whose policy would be rejected AWS-side, rather than ship a
23
+ * silently-broadened or truncated policy. Shared with `deploySessionPolicy.ts`
24
+ * — both builders guard the same AWS limit.
25
+ */
26
+ export declare const MAX_INLINE_POLICY_CHARS = 2048;
19
27
  /**
20
28
  * Derive the `aws` / `aws-cn` partition from a region. China regions use the
21
29
  * `aws-cn` partition; everything else (incl. GovCloud `us-gov-*`, which is
@@ -1 +1 @@
1
- import{success as $,failure as g}from"@fjall/generator";import{toKebab as p}from"@fjall/util";const l=2048;function w(s){return s.startsWith("cn-")?"aws-cn":"aws"}function R(s,f){const{region:r,accountId:n,partition:a}=s,i=p(s.appName),u=`arn:${a}:ssm:${r}:${n}:parameter/${i}/*`,m=`arn:${a}:secretsmanager:${r}:${n}:secret:fjall-${i}-*`,t=new Set;for(const e of f){if(e.ssm!==void 0&&e.ssm!==""){const c=e.ssm.startsWith("/")?e.ssm.slice(1):e.ssm;c.startsWith(`${i}/`)||t.add(`arn:${a}:ssm:${r}:${n}:parameter/${c}`)}if(e.secretsManagerArn!==void 0&&e.secretsManagerArn!==""){const c=m.replace(/\*$/,"");e.secretsManagerArn.startsWith(c)||t.add(e.secretsManagerArn)}e.secretsManagerName!==void 0&&e.secretsManagerName!==""&&!e.secretsManagerName.startsWith(`fjall-${i}-`)&&t.add(`arn:${a}:secretsmanager:${r}:${n}:secret:${e.secretsManagerName}-*`)}const d=[{Sid:"FjallBuildSsmRead",Effect:"Allow",Action:["ssm:GetParameter","ssm:GetParameters"],Resource:[u,...[...t].filter(S)]},{Sid:"FjallBuildSecretsManagerRead",Effect:"Allow",Action:["secretsmanager:GetSecretValue"],Resource:[m,...[...t].filter(A)]},{Sid:"FjallBuildKmsDecrypt",Effect:"Allow",Action:["kms:Decrypt"],Resource:"*",Condition:{StringEquals:{"kms:ViaService":[`ssm.${r}.amazonaws.com`,`secretsmanager.${r}.amazonaws.com`]}}}],o=JSON.stringify({Version:"2012-10-17",Statement:d});return o.length>l?g(new Error(`Build-secret session policy is ${o.length} chars, exceeding the ${l}-char AWS inline-policy limit. Reduce the number of out-of-prefix secret/parameter references for this build.`)):$(o)}function S(s){return s.includes(":ssm:")}function A(s){return s.includes(":secretsmanager:")}export{R as buildBuildSecretSessionPolicy,w as partitionForRegion};
1
+ import{success as $,failure as g}from"@fjall/generator";import{toKebab as p}from"@fjall/util";const l=2048;function w(s){return s.startsWith("cn-")?"aws-cn":"aws"}function R(s,f){const{region:r,accountId:n,partition:a}=s,i=p(s.appName),u=`arn:${a}:ssm:${r}:${n}:parameter/${i}/*`,m=`arn:${a}:secretsmanager:${r}:${n}:secret:fjall-${i}-*`,t=new Set;for(const e of f){if(e.ssm!==void 0&&e.ssm!==""){const c=e.ssm.startsWith("/")?e.ssm.slice(1):e.ssm;c.startsWith(`${i}/`)||t.add(`arn:${a}:ssm:${r}:${n}:parameter/${c}`)}if(e.secretsManagerArn!==void 0&&e.secretsManagerArn!==""){const c=m.replace(/\*$/,"");e.secretsManagerArn.startsWith(c)||t.add(e.secretsManagerArn)}e.secretsManagerName!==void 0&&e.secretsManagerName!==""&&!e.secretsManagerName.startsWith(`fjall-${i}-`)&&t.add(`arn:${a}:secretsmanager:${r}:${n}:secret:${e.secretsManagerName}-*`)}const d=[{Sid:"FjallBuildSsmRead",Effect:"Allow",Action:["ssm:GetParameter","ssm:GetParameters"],Resource:[u,...[...t].filter(S)]},{Sid:"FjallBuildSecretsManagerRead",Effect:"Allow",Action:["secretsmanager:GetSecretValue"],Resource:[m,...[...t].filter(A)]},{Sid:"FjallBuildKmsDecrypt",Effect:"Allow",Action:["kms:Decrypt"],Resource:"*",Condition:{StringEquals:{"kms:ViaService":[`ssm.${r}.amazonaws.com`,`secretsmanager.${r}.amazonaws.com`]}}}],o=JSON.stringify({Version:"2012-10-17",Statement:d});return o.length>l?g(new Error(`Build-secret session policy is ${o.length} chars, exceeding the ${l}-char AWS inline-policy limit. Reduce the number of out-of-prefix secret/parameter references for this build.`)):$(o)}function S(s){return s.includes(":ssm:")}function A(s){return s.includes(":secretsmanager:")}export{l as MAX_INLINE_POLICY_CHARS,R as buildBuildSecretSessionPolicy,w as partitionForRegion};
@@ -56,3 +56,10 @@ export declare function rebaseRollbackTagForService(suppliedTag: string, service
56
56
  * un-rebased tag would silently roll the wrong service's image).
57
57
  */
58
58
  export declare function deployCodeOnly(params: DeployParams, services: DeployServices, operation: ApplicationOperation, contentHashTagsByService: Record<string, string>): Promise<Result<DeployResult>>;
59
+ /**
60
+ * Derive an ECR repository ARN from an image URI of shape
61
+ * `<account>.dkr.ecr.<region>.amazonaws.com/<repo>`. Returns "" when the URI
62
+ * does not match the ECR pattern (e.g. third-party registries) so the caller
63
+ * can persist the imageUri without a misleading ARN.
64
+ */
65
+ export declare function deriveEcrRepositoryArn(repoUri: string): string;
@@ -1 +1 @@
1
- import{join as _}from"path";import{success as j,failure as S}from"@fjall/generator";import{logger as L}from"@fjall/util/logger";import{maskSensitiveOutput as v}from"@fjall/util";import{parseDockerServicesFromManifest as N}from"@fjall/util/manifest";import{APPLICATION_STACKS as F,getApplicationStepId as W,getApplicationStepName as q}from"../../types/operations.js";import{extractEcsServiceName as O}from"../../aws/utils/arnParser.js";import{stripTagOrDigest as U}from"../../services/infrastructure/EcrImageInspectorService.js";import{withStepLifecycle as z}from"../stepLifecycle.js";const I="codeOnlyDeploy";function G(n,a){if(n)return a[n.name]}function H(n,a){const o=a.toLowerCase();return n.find(t=>t.name.toLowerCase()===o)??n.find(t=>o.includes(t.name.toLowerCase()))}function K(n,a,o){const t=a.toLowerCase(),c=n.toLowerCase(),g=o.map(p=>p.toLowerCase()).filter(p=>p!==""&&c.startsWith(`${p}-`)).sort((p,T)=>T.length-p.length)[0];if(g===void 0||g===t)return n;const E=n.slice(g.length);return`${t}${E}`}const X=0,Y=1;async function ae(n,a,o,t){const{callbacks:c,options:g}=n,E=Date.now(),p=W(F.COMPUTE,"deploy"),T=q(F.COMPUTE,"deploy");return z(c,{stepId:p,stepName:T,stepIndex:X,totalSteps:Y},async()=>{const d=g?.imageTag;d&&c.onLog?.(`Rolling to supplied image tag ${d}`,"info");const D=N(_(o.path,"cdk.out"));L.debug(I,"Code-only rollout starting",{explicitRollbackTag:d,workingDirectory:n.workingDirectory,contentHashTagsByService:t,manifestServiceCount:D.length,manifestServices:D.map(e=>({name:e.name,target:e.docker.target}))}),c.onLog?.("Discovering ECS cluster and services\u2026","info");const h=await a.ecsResolver.getDeployableClusterAndServices(o.appName);if(!h.success)return L.debug(I,"ECS discovery failed",{appName:o.appName,error:v(h.error.message)}),{kind:"error",error:h.error};const{clusterArn:r,serviceArns:m}=h.data;if(L.debug(I,"ECS discovery complete",{appName:o.appName,clusterArn:r,serviceCount:m.length,serviceArns:m}),!r||m.length===0){const e=`No deployable ECS services found for ${o.appName}`;return c.onLog?.(e,"warn"),{kind:"skipped",data:{target:o.appName,deploymentType:"application",durationMs:Date.now()-E,noChanges:!0}}}const b=g?.serviceName?.toLowerCase(),$=b===""?void 0:b,f=$?m.filter(e=>{const l=(O(e)??"").toLowerCase();return l===$||l.endsWith($)}):m;if($&&f.length===0){const e=m.map(l=>O(l)??"unknown").join(", ");return{kind:"error",error:new Error(`Service '${$}' not found. Available: ${e||"none"}`)}}const w={},k=[],R=m.map(e=>O(e)).filter(e=>e!==void 0&&e!=="");for(let e=0;e<f.length;e++){const l=f[e];if(!l)continue;const i=O(l)??`service-${e+1}`,A=`[${e+1}/${f.length}] ${i}`,y=H(D,i),x=G(y,t);let C,s;if(d!==void 0)C=K(d,i,R),C!==d?(s=d,c.onLog?.(`Re-based image tag ${d} \u2192 ${C} for ${i}`,"info")):R.length>1&&!d.toLowerCase().startsWith(`${i.toLowerCase()}-`)&&c.onLog?.(`Image tag ${d} has no recognised service prefix; applying verbatim to ${i} \u2014 confirm it points at this service's image`,"warn");else if(x!==void 0)C=x;else{const P=`No image available to deploy for ${i}: no content-hash tag was produced this run. Run a full deploy to build the image, or pass --image-tag <tag> to roll a specific existing image.`;c.onLog?.(P,"warn"),k.push(`${i}: ${P}`);continue}L.debug(I,"Starting service rollout",{serviceName:i,serviceArn:l,imageTag:C,explicitRollbackTag:d,contentHashTag:x,manifestServiceMatched:y?.name,dockerTarget:y?.docker.target,index:e,total:f.length}),c.onECSUpdate?.(`${A}: rolling out image tag ${C}`,i);const u=await J(a,r,l,i,C,c,s);if(L.debug(I,"Service rollout completed",{serviceName:i,success:u.success,...u.success?{taskDefinitionArn:u.data.taskDefinitionArn,imageDigest:u.data.imageDigest,durationMs:u.data.durationMs}:{error:v(u.error.message)}}),!u.success){k.push(`${i}: ${v(u.error.message)}`);continue}w[`${i}TaskDefinition`]=u.data.taskDefinitionArn,w[`${i}PreviousTaskDefinition`]=u.data.previousTaskDefinitionArn,w[`${i}ImageTag`]=C,w[`${i}ImageUri`]=u.data.imageUri,w[`${i}EcrRepositoryArn`]=u.data.ecrRepositoryArn,u.data.imageDigest!==void 0&&(w[`${i}ImageDigest`]=u.data.imageDigest)}const M=Date.now()-E;if(k.length>0){const e=f.length-k.length,l=f.length,i=k.length===l?`All ${l} ECS service rollouts failed: ${k.join("; ")}`:`Partial rollout: ${e}/${l} services succeeded. Failures: ${k.join("; ")}`;return{kind:"error",error:new Error(i)}}return{kind:"completed",data:{target:o.appName,deploymentType:"application",outputs:Object.keys(w).length>0?w:void 0,durationMs:M}}},n.abortSignal)}async function J(n,a,o,t,c,g,E){const p=Date.now(),T=await n.ecsService.describeServices(a,[o]);if(!T.success)return S(new Error(`Failed to describe service ${t}: ${T.error.message}`));const d=T.data[0];if(!d?.taskDefinition)return S(new Error(`Service ${t} has no task definition`));const D=d.taskDefinition,h=await n.ecsService.getLatestTaskDefinition(D);if(!h.success)return S(new Error(`Failed to fetch task definition for ${t}: ${h.error.message}`));const r=h.data;if(!r||!r.containerDefinitions||r.containerDefinitions.length===0)return S(new Error(`Task definition for ${t} has no container definitions`));const m=r.containerDefinitions.find(s=>s.image)?.image;if(!m)return S(new Error(`Task definition for ${t} has no container image \u2014 cannot derive repository URI`));const b=U(m),$=await n.ecrImageInspector.getImageDigest(b,c);let f;if($.success)f=$.data;else{if(E!==void 0)return S(new Error(`Re-based image tag '${c}' (from supplied '${E}') was not found in ECR for ${t}: ${v($.error.message)}. The supplied tag's build has no image for ${t} \u2014 supply that service's own tag, or pass --service to roll a single service.`));g.onLog?.(`Could not resolve image digest for ${t} (${v($.error.message)}); falling back to tag pinning`,"warn")}const w=s=>f!==void 0?`${s}@${f}`:`${s}:${c}`,k=r.containerDefinitions.map(s=>s.image?{...s,image:w(U(s.image))}:s),R=r.family;if(!R)return S(new Error(`Task definition for ${t} has no family`));const M=await n.ecsService.registerTaskDefinition({family:R,taskRoleArn:r.taskRoleArn,executionRoleArn:r.executionRoleArn,networkMode:r.networkMode,containerDefinitions:k,volumes:r.volumes,placementConstraints:r.placementConstraints,requiresCompatibilities:r.requiresCompatibilities,cpu:r.cpu,memory:r.memory,runtimePlatform:r.runtimePlatform,proxyConfiguration:r.proxyConfiguration,inferenceAccelerators:r.inferenceAccelerators,pidMode:r.pidMode,ipcMode:r.ipcMode,ephemeralStorage:r.ephemeralStorage});if(!M.success)return S(new Error(`Failed to register task definition for ${t}: ${M.error.message}`));const e=M.data;g.onTaskDefRegistered?.({serviceName:t,taskDefinitionArn:e,previousTaskDefinitionArn:D,revision:V(e),family:R,...f!==void 0?{imageDigest:f}:{}});const l=await n.ecsService.updateService(a,o,{taskDefinition:e,forceNewDeployment:!0});if(!l.success)return S(new Error(`Failed to update service ${t}: ${l.error.message}`));const i=await n.ecsService.pollDeployment({clusterArn:a,serviceArn:o,waitForCompletion:!0,progressCallback:s=>{const u=s.latestEvent?` \u2014 ${v(s.latestEvent)}`:"";g.onECSProgress?.(`${t}: ${v(s.message)}${u}`,s.percentComplete)}}),A=Date.now()-p;if(!i.success){const s=v(i.error.message);return g.onECSComplete?.({serviceName:t,serviceArn:o,success:!1,taskDefinitionArn:e,durationMs:A,reason:s}),S(new Error(`ECS rollout failed for ${t}: ${s}`))}const y=await n.ecsService.getDeploymentStatus(a,o);g.onECSComplete?.({serviceName:t,serviceArn:o,success:!0,taskDefinitionArn:e,durationMs:A,finalRunningCount:y.success?y.data.runningCount:void 0,finalDesiredCount:y.success?y.data.desiredCount:void 0}),L.debug(I,`Rolled out ${t} successfully`,{serviceArn:o,newTaskDefArn:e,previousTaskDefArn:D,durationMs:A});const x=`${b}:${c}`,C=Q(b);return j({taskDefinitionArn:e,previousTaskDefinitionArn:D,imageUri:x,ecrRepositoryArn:C,imageDigest:f,durationMs:A})}function Q(n){const a=n.match(/^(\d{12})\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com\/(.+)$/);if(!a)return"";const[,o,t,c]=a;return`arn:aws:ecr:${t}:${o}:repository/${c}`}function V(n){const a=n.lastIndexOf(":");if(a<0)return 0;const o=n.slice(a+1);return/^\d+$/.test(o)?parseInt(o,10):0}export{ae as deployCodeOnly,G as lookupContentHashTag,K as rebaseRollbackTagForService};
1
+ import{join as N}from"path";import{success as W,failure as C}from"@fjall/generator";import{logger as b}from"@fjall/util/logger";import{artefactOutputKey as L,maskSensitiveOutput as y}from"@fjall/util";import{parseDockerServicesFromManifest as q}from"@fjall/util/manifest";import{APPLICATION_STACKS as _,getApplicationStepId as z,getApplicationStepName as G}from"../../types/operations.js";import{extractEcsServiceName as U}from"../../aws/utils/arnParser.js";import{stripTagOrDigest as j}from"../../services/infrastructure/EcrImageInspectorService.js";import{withStepLifecycle as K}from"../stepLifecycle.js";const I="codeOnlyDeploy";function H(t,c){if(t)return c[t.name]}function X(t,c){const r=c.toLowerCase();return t.find(e=>e.name.toLowerCase()===r)??t.find(e=>r.includes(e.name.toLowerCase()))}function Y(t,c,r){const e=c.toLowerCase(),l=t.toLowerCase(),g=r.map(m=>m.toLowerCase()).filter(m=>m!==""&&l.startsWith(`${m}-`)).sort((m,E)=>E.length-m.length)[0];if(g===void 0||g===e)return t;const v=t.slice(g.length);return`${e}${v}`}const J=0,Q=1;async function le(t,c,r,e){const{callbacks:l,options:g}=t,v=Date.now(),m=z(_.COMPUTE,"deploy"),E=G(_.COMPUTE,"deploy");return K(l,{stepId:m,stepName:E,stepIndex:J,totalSteps:Q},async()=>{const d=g?.imageTag;d&&l.onLog?.(`Rolling to supplied image tag ${d}`,"info");const S=q(N(r.path,"cdk.out"));b.debug(I,"Code-only rollout starting",{explicitRollbackTag:d,workingDirectory:t.workingDirectory,contentHashTagsByService:e,manifestServiceCount:S.length,manifestServices:S.map(i=>({name:i.name,target:i.docker.target}))}),l.onLog?.("Discovering ECS cluster and services\u2026","info");const h=await c.ecsResolver.getDeployableClusterAndServices(r.appName);if(!h.success)return b.debug(I,"ECS discovery failed",{appName:r.appName,error:y(h.error.message)}),{kind:"error",error:h.error};const{clusterArn:a,serviceArns:$}=h.data;if(b.debug(I,"ECS discovery complete",{appName:r.appName,clusterArn:a,serviceCount:$.length,serviceArns:$}),!a||$.length===0){const i=`No deployable ECS services found for ${r.appName}`;return l.onLog?.(i,"warn"),{kind:"skipped",data:{target:r.appName,deploymentType:"application",durationMs:Date.now()-v,noChanges:!0,artefacts:[]}}}const T=g?.serviceName?.toLowerCase(),w=T===""?void 0:T,f=w?$.filter(i=>{const u=(U(i)??"").toLowerCase();return u===w||u.endsWith(w)}):$;if(w&&f.length===0){const i=$.map(u=>U(u)??"unknown").join(", ");return{kind:"error",error:new Error(`Service '${w}' not found. Available: ${i||"none"}`)}}const D={},O=[],p=[],A=$.map(i=>U(i)).filter(i=>i!==void 0&&i!=="");for(let i=0;i<f.length;i++){const u=f[i];if(!u)continue;const n=U(u)??`service-${i+1}`,R=`[${i+1}/${f.length}] ${n}`,M=X(S,n),x=H(M,e);let o,P;if(d!==void 0)o=Y(d,n,A),o!==d?(P=d,l.onLog?.(`Re-based image tag ${d} \u2192 ${o} for ${n}`,"info")):A.length>1&&!d.toLowerCase().startsWith(`${n.toLowerCase()}-`)&&l.onLog?.(`Image tag ${d} has no recognised service prefix; applying verbatim to ${n} \u2014 confirm it points at this service's image`,"warn");else if(x!==void 0)o=x;else{const F=`No image available to deploy for ${n}: no content-hash tag was produced this run. Run a full deploy to build the image, or pass --image-tag <tag> to roll a specific existing image.`;l.onLog?.(F,"warn"),p.push(`${n}: ${F}`);continue}b.debug(I,"Starting service rollout",{serviceName:n,serviceArn:u,imageTag:o,explicitRollbackTag:d,contentHashTag:x,manifestServiceMatched:M?.name,dockerTarget:M?.docker.target,index:i,total:f.length}),l.onECSUpdate?.(`${R}: rolling out image tag ${o}`,n);const s=await V(c,a,u,n,o,l,P);if(b.debug(I,"Service rollout completed",{serviceName:n,success:s.success,...s.success?{taskDefinitionArn:s.data.taskDefinitionArn,imageDigest:s.data.imageDigest,durationMs:s.data.durationMs}:{error:y(s.error.message)}}),!s.success){p.push(`${n}: ${y(s.error.message)}`);continue}D[L(n,"TaskDefinition")]=s.data.taskDefinitionArn,D[L(n,"PreviousTaskDefinition")]=s.data.previousTaskDefinitionArn,D[L(n,"ImageTag")]=o,D[L(n,"ImageUri")]=s.data.imageUri,D[L(n,"EcrRepositoryArn")]=s.data.ecrRepositoryArn,s.data.imageDigest!==void 0&&(D[L(n,"ImageDigest")]=s.data.imageDigest),O.push({serviceName:n,imageTag:o,imageUri:s.data.imageUri,taskDefinitionArn:s.data.taskDefinitionArn,previousTaskDefinitionArn:s.data.previousTaskDefinitionArn,...s.data.imageDigest!==void 0?{imageDigest:s.data.imageDigest}:{},...s.data.ecrRepositoryArn!==""?{ecrRepositoryArn:s.data.ecrRepositoryArn}:{}})}const k=Date.now()-v;if(p.length>0){const i=f.length-p.length,u=f.length,n=p.length===u?`All ${u} ECS service rollouts failed: ${p.join("; ")}`:`Partial rollout: ${i}/${u} services succeeded. Failures: ${p.join("; ")}`;return{kind:"error",error:new Error(n)}}return{kind:"completed",data:{target:r.appName,deploymentType:"application",outputs:Object.keys(D).length>0?D:void 0,artefacts:O,durationMs:k}}},t.abortSignal)}async function V(t,c,r,e,l,g,v){const m=Date.now(),E=await t.ecsService.describeServices(c,[r]);if(!E.success)return C(new Error(`Failed to describe service ${e}: ${E.error.message}`));const d=E.data[0];if(!d?.taskDefinition)return C(new Error(`Service ${e} has no task definition`));const S=d.taskDefinition,h=await t.ecsService.getLatestTaskDefinition(S);if(!h.success)return C(new Error(`Failed to fetch task definition for ${e}: ${h.error.message}`));const a=h.data;if(!a||!a.containerDefinitions||a.containerDefinitions.length===0)return C(new Error(`Task definition for ${e} has no container definitions`));const $=a.containerDefinitions.find(o=>o.image)?.image;if(!$)return C(new Error(`Task definition for ${e} has no container image \u2014 cannot derive repository URI`));const T=j($),w=await t.ecrImageInspector.getImageDigest(T,l);let f;if(w.success)f=w.data;else{if(v!==void 0)return C(new Error(`Re-based image tag '${l}' (from supplied '${v}') was not found in ECR for ${e}: ${y(w.error.message)}. The supplied tag's build has no image for ${e} \u2014 supply that service's own tag, or pass --service to roll a single service.`));g.onLog?.(`Could not resolve image digest for ${e} (${y(w.error.message)}); falling back to tag pinning`,"warn")}const D=o=>f!==void 0?`${o}@${f}`:`${o}:${l}`,O=a.containerDefinitions.map(o=>o.image?{...o,image:D(j(o.image))}:o),p=a.family;if(!p)return C(new Error(`Task definition for ${e} has no family`));const A=await t.ecsService.registerTaskDefinition({family:p,taskRoleArn:a.taskRoleArn,executionRoleArn:a.executionRoleArn,networkMode:a.networkMode,containerDefinitions:O,volumes:a.volumes,placementConstraints:a.placementConstraints,requiresCompatibilities:a.requiresCompatibilities,cpu:a.cpu,memory:a.memory,runtimePlatform:a.runtimePlatform,proxyConfiguration:a.proxyConfiguration,inferenceAccelerators:a.inferenceAccelerators,pidMode:a.pidMode,ipcMode:a.ipcMode,ephemeralStorage:a.ephemeralStorage});if(!A.success)return C(new Error(`Failed to register task definition for ${e}: ${A.error.message}`));const k=A.data;g.onTaskDefRegistered?.({serviceName:e,taskDefinitionArn:k,previousTaskDefinitionArn:S,revision:B(k),family:p,...f!==void 0?{imageDigest:f}:{}});const i=await t.ecsService.updateService(c,r,{taskDefinition:k,forceNewDeployment:!0});if(!i.success)return C(new Error(`Failed to update service ${e}: ${i.error.message}`));const u=await t.ecsService.pollDeployment({clusterArn:c,serviceArn:r,waitForCompletion:!0,progressCallback:o=>{const P=o.latestEvent?` \u2014 ${y(o.latestEvent)}`:"";g.onECSProgress?.(`${e}: ${y(o.message)}${P}`,o.percentComplete)}}),n=Date.now()-m;if(!u.success){const o=y(u.error.message);return g.onECSComplete?.({serviceName:e,serviceArn:r,success:!1,taskDefinitionArn:k,durationMs:n,reason:o}),C(new Error(`ECS rollout failed for ${e}: ${o}`))}const R=await t.ecsService.getDeploymentStatus(c,r);g.onECSComplete?.({serviceName:e,serviceArn:r,success:!0,taskDefinitionArn:k,durationMs:n,finalRunningCount:R.success?R.data.runningCount:void 0,finalDesiredCount:R.success?R.data.desiredCount:void 0}),b.debug(I,`Rolled out ${e} successfully`,{serviceArn:r,newTaskDefArn:k,previousTaskDefArn:S,durationMs:n});const M=`${T}:${l}`,x=Z(T);return W({taskDefinitionArn:k,previousTaskDefinitionArn:S,imageUri:M,ecrRepositoryArn:x,imageDigest:f,durationMs:n})}function Z(t){const c=t.match(/^(\d{12})\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com\/(.+)$/);if(!c)return"";const[,r,e,l]=c;return`arn:aws:ecr:${e}:${r}:repository/${l}`}function B(t){const c=t.lastIndexOf(":");if(c<0)return 0;const r=t.slice(c+1);return/^\d+$/.test(r)?parseInt(r,10):0}export{le as deployCodeOnly,Z as deriveEcrRepositoryArn,H as lookupContentHashTag,Y as rebaseRollbackTagForService};
@@ -0,0 +1,46 @@
1
+ import { type Result } from "@fjall/generator";
2
+ /**
3
+ * The AWS-account/region context a deploy session's inline policy is scoped to.
4
+ * `appName` is the deploy target; `partition` is derived via `partitionForRegion`
5
+ * (re-exported from `buildSecretSession.js`).
6
+ */
7
+ export interface DeploySessionContext {
8
+ appName: string;
9
+ region: string;
10
+ accountId: string;
11
+ partition: string;
12
+ }
13
+ /**
14
+ * Build a defence-in-depth inline session policy that narrows a 1-hour deploy
15
+ * session's DIRECT CloudFormation/ECR mutation to the target app's stack-name
16
+ * prefix and ECR repository. The connected `FjallDeploy<orgId>` role carries
17
+ * `AdministratorAccess` and the OIDC subject encodes no resource narrowing, so
18
+ * without this the session is an org-wide admin bearer credential.
19
+ *
20
+ * Two accepted v1 limitations (not app-confinement guarantees):
21
+ * - Session policies do not propagate across `sts:AssumeRole`, so mutation via
22
+ * the CDK deploy/cfn-exec roles is out of this policy's reach — the real
23
+ * confinement is the Phase E `FjallDeployBoundary` permission boundary.
24
+ * - The `stack/${appPascal}*` prefix wildcard over-grants a prefix-collision
25
+ * sibling app (`Web` vs `WebApp`) — same low-risk, same-account class as the
26
+ * ECR baseline-prefix note in `buildSecretSession.ts`.
27
+ *
28
+ * DENY-list, not allow-list (deliberate). `fjall deploy` runs `cdk deploy`: the
29
+ * caller's session performs synth lookups, asset uploads to the CDK asset bucket,
30
+ * ECR pushes, `iam:PassRole` on the cfn-exec role, and `CreateStack`/`UpdateStack`.
31
+ * CloudFormation then provisions all downstream resources under the pre-bootstrapped
32
+ * cfn-exec role (AdministratorAccess), NOT under this session. An allow-list scoped
33
+ * to "app stacks + ECR" would strip synth/asset/PassRole and break every deploy, so
34
+ * we instead lead with `Allow * on *` (inherit the role's admin) and Deny only
35
+ * FOREIGN CloudFormation-stack mutation and FOREIGN ECR mutation.
36
+ *
37
+ * Naming is coupled to `getApplicationStackName` (`../../types/operations.js`):
38
+ * every one of an app's stacks is named `toPascalCase(appName) + stack`, so a
39
+ * CloudFormation stack-ARN wildcard on the `toPascalCase(appName)` prefix covers
40
+ * all six. If that derivation ever changes, this wildcard must change with it. The
41
+ * ECR repo is `toKebab(appName)` (matching the ECR construct at
42
+ * components/infrastructure/lib/app.ts) — never a re-implemented `.toLowerCase()`.
43
+ *
44
+ * Fails closed when the serialised policy would exceed the AWS inline-policy limit.
45
+ */
46
+ export declare function buildDeploySessionPolicy(ctx: DeploySessionContext): Result<string>;
@@ -0,0 +1 @@
1
+ import{success as p,failure as s}from"@fjall/generator";import{toKebab as m,toPascalCase as d}from"@fjall/util";import{MAX_INLINE_POLICY_CHARS as r}from"./buildSecretSession.js";const u=["cloudformation:CreateStack","cloudformation:UpdateStack","cloudformation:DeleteStack","cloudformation:CreateChangeSet","cloudformation:ExecuteChangeSet","cloudformation:DeleteChangeSet","cloudformation:CancelUpdateStack","cloudformation:ContinueUpdateRollback","cloudformation:RollbackStack","cloudformation:SetStackPolicy","cloudformation:UpdateTerminationProtection"],f=["ecr:PutImage","ecr:InitiateLayerUpload","ecr:UploadLayerPart","ecr:CompleteLayerUpload","ecr:BatchDeleteImage","ecr:DeleteRepository","ecr:SetRepositoryPolicy","ecr:DeleteRepositoryPolicy"];function $(a){const{region:o,accountId:e,partition:t}=a,n=d(a.appName),i=m(a.appName),l=[{Sid:"FjallDeployInheritAdmin",Effect:"Allow",Action:"*",Resource:"*"},{Sid:"FjallDeployDenyForeignStackMutation",Effect:"Deny",Action:u,NotResource:[`arn:${t}:cloudformation:${o}:${e}:stack/${n}*/*`,`arn:${t}:cloudformation:${o}:${e}:stack/CDKToolkit/*`]},{Sid:"FjallDeployDenyForeignEcrMutation",Effect:"Deny",Action:f,NotResource:[`arn:${t}:ecr:${o}:${e}:repository/${i}`,`arn:${t}:ecr:${o}:${e}:repository/cdk-*`]}],c=JSON.stringify({Version:"2012-10-17",Statement:l});return c.length>r?s(new Error(`Deploy session policy is ${c.length} chars, exceeding the ${r}-char AWS inline-policy limit.`)):p(c)}export{$ as buildDeploySessionPolicy};
@@ -4,6 +4,22 @@ import type { DeployCallbacks } from "../../types/callbacks.js";
4
4
  import type { ApplicationOperation } from "../../types/operations.js";
5
5
  import type { DeployServices } from "../serviceFactory.js";
6
6
  export declare const DOCKER_BUILD_STEP_NAME = "Building and pushing Docker image";
7
+ /**
8
+ * Everything the build phase knows about what it pushed. The tag map drives
9
+ * the CDK `<Service>ImageTag` parameters; the digest map + repo URI feed the
10
+ * full-deploy `ServiceArtefact[]` collection.
11
+ */
12
+ export interface DockerBuildProducts {
13
+ contentHashTagsByService: Record<string, string>;
14
+ imageDigestsByService: Record<string, string>;
15
+ /**
16
+ * ECR repository URI the images were pushed to. Absent on the legacy
17
+ * single-image fallback path (no manifest), where no content-hash tags are
18
+ * produced either.
19
+ */
20
+ repoUri?: string;
21
+ }
22
+ export declare function emptyBuildProducts(): DockerBuildProducts;
7
23
  /**
8
24
  * Run Docker build and push before deploying the Compute stack.
9
25
  *
@@ -16,12 +32,12 @@ export declare const DOCKER_BUILD_STEP_NAME = "Building and pushing Docker image
16
32
  * After each group's push, the helper derives a content-hash tag from the
17
33
  * digest (`deriveContentHashTag` at `@fjall/util`) and applies it via
18
34
  * `tagByDigest` (manifest-list create, no layer re-upload). The resulting
19
- * `<service>` → `<content-hash-tag>` map is returned so the orchestrator can
20
- * plumb it into the CDK deploy via `--parameters <Service>ImageTag=<tag>`.
35
+ * `DockerBuildProducts` carries the `<service>` → `<content-hash-tag>` map
36
+ * (plumbed into the CDK deploy via `--parameters <Service>ImageTag=<tag>`)
37
+ * plus the per-service digests and repo URI for artefact collection.
21
38
  *
22
39
  * Falls back to the legacy single-image path (`<appPath>/Dockerfile` →
23
40
  * `<appName>:latest`) when the manifest is absent. The legacy path returns
24
- * an empty content-hash tag map; the orchestrator skips parameter plumbing
25
- * for that branch.
41
+ * empty products; the orchestrator skips parameter plumbing for that branch.
26
42
  */
27
- export declare function runDockerBuild(params: DeployParams, services: DeployServices, operation: ApplicationOperation, callbacks: DeployCallbacks): Promise<Result<Record<string, string>>>;
43
+ export declare function runDockerBuild(params: DeployParams, services: DeployServices, operation: ApplicationOperation, callbacks: DeployCallbacks): Promise<Result<DockerBuildProducts>>;
@@ -1 +1 @@
1
- import{isAbsolute as _,join as B,dirname as j,resolve as $}from"node:path";import{existsSync as x}from"node:fs";import{success as k,failure as l}from"@fjall/generator";import{deriveContentHashTag as F,maskSensitiveOutput as f,toKebab as I}from"@fjall/util";import{evaluateBakeGuard as U,evaluateResolvedBuildArgValues as G,acknowledgedBuildArgKeys as H,BAKE_GUARD_EXPOSURE_CLAUSE as V}from"@fjall/util/docker";import{logger as C}from"@fjall/util/logger";import{parseDockerServicesFromManifest as M}from"@fjall/util/manifest";import{STEP_IDS as y}from"../../types/stepDefinitions.js";import{resolveBuildSecrets as W,resolveSecretRefValue as z,resolveBuildSecretSessionProvider as X,sourcedRefsFromBuildArgs as Y}from"./buildSecretResolver.js";import{partitionForRegion as q}from"./buildSecretSession.js";import{resolveBuildArgs as J}from"./buildArgResolver.js";import{validateBuildGroup as Q}from"./buildGroupValidator.js";const P="dockerBuildHelper",E="Building and pushing Docker image";function Z(e,s,t){const r=B(s,e);if(x(r))return{path:r,base:s};if(t!==""&&t!==s){const o=B(t,e);if(x(o))return{path:o,base:t}}return{path:r,base:s}}function ee(e,s,t){const r=e.docker.path,o=e.docker.context;let a,i;if(_(r))a=r,i=s;else{const g=Z(r,s,t);a=g.path,i=g.base}return{buildContext:o?_(o)?$(o):$(i,o):$(j(a)),dockerfilePath:a,target:e.docker.target,resolvedViaFallback:i!==s}}function re(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function te(e,s){return`Dockerfile not found for service '${e}' at ${s}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function ne(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function oe(e,s,t){return`${e}.dkr.ecr.${s}.${ne(s)}/${I(t)}`}function ie(e,s,t){const r=new Map;for(const o of e){const a=ee(o,s,t),i=re(a),m=r.get(i);m?m.members.push(o):r.set(i,{identity:a,members:[o]})}return Array.from(r.values())}async function se(e,s,t,r,o){const a=await e.buildAndPush({appName:s.appName,appPath:s.path,region:t,accountId:r},(i,m,g,v)=>{o.onDockerProgress?.(f(i),m,g,v)});return a.success?k({imageUri:a.data.imageUri,imageTag:a.data.imageTag}):l(a.error)}async function ae(e,s,t,r,o,a,i,m,g,v,p,h){const u=e.members[0];if(!x(e.identity.dockerfilePath))return l(new Error(te(u.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&i.onLog?.(`Building service '${u.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const n=[...u.docker.buildSecrets??[],...h??[]],c=Q({serviceName:u.name,dockerfilePath:e.identity.dockerfilePath,buildContext:e.identity.buildContext,...e.identity.target!==void 0&&{target:e.identity.target},...u.docker.buildArgs!==void 0&&{buildArgs:u.docker.buildArgs},...n.length>0&&{buildSecrets:n},members:e.members.map(d=>({serviceName:d.name,...d.docker.buildArgs!==void 0&&{buildArgs:d.docker.buildArgs},...d.docker.buildSecrets!==void 0&&{buildSecrets:d.docker.buildSecrets}}))});if(!c.success)return l(c.error);const S=U(u.docker.buildArgs,I(t.appName));for(const d of S.warnings)i.onLog?.(f(d.message),"warn");if(S.findings.length>0)return l(new Error(f(S.findings.map(d=>d.message).join(" "))));const R=n,A=Y(u.docker.buildArgs),b={appName:t.appName,region:r,accountId:o,partition:q(r)},w=await X(m,[...R,...A],b);if(!w.success)return l(w.error);const T=w.data,N=await J({manifestBuildArgs:u.docker.buildArgs,serviceDir:e.identity.buildContext,appDir:t.path,...v!==void 0&&{stage:v},shellEnv:process.env,...p!==void 0&&{flagBuildArgs:p},resolveSourcedValue:(d,K)=>z(T,{id:d,...K},g)});if(!N.success)return l(N.error);const O=N.data.buildArgs;for(const d of N.data.inferredDotenvKeys)i.onLog?.(f(`buildArg "${d}" was auto-declared from a .env file and will be baked into the image \u2014 ${V}.`),"warn");const L=G(O,I(t.appName),H(u.docker.buildArgs));if(L.length>0)return l(new Error(f(L.map(d=>d.message).join(" "))));let D;if(R.length>0){const d=await W(T,R,g);if(!d.success)return l(d.error);D=d.data}try{const d={appName:t.appName,appPath:e.identity.buildContext,region:r,accountId:o,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,pushByDigest:!0,serviceName:u.name,...e.identity.target!==void 0&&{target:e.identity.target},...Object.keys(O).length>0&&{buildArgs:O},...D!==void 0&&{buildSecrets:D.resolved}};return await de(e,d,s,a,i)}finally{D!==void 0&&await D.cleanup().catch(()=>i.onLog?.("Build-secret cleanup failed.","warn"))}}async function de(e,s,t,r,o){const a=await t.buildAndPush(s,(p,h,u,n)=>{o.onDockerProgress?.(f(p),h,u,n)});if(!a.success)return l(a.error);const i=a.data.imageDigest;if(typeof i!="string"||!i.startsWith("sha256:"))return l(new Error(`Buildx push succeeded but returned an invalid digest: ${f(String(i))}`));const m={},g=[];for(const p of e.members){const h=p.docker.target,u=F(i,p.name,h);if(!u.success)return l(u.error);const n=u.data,c=p.name;m[c]=n,g.push(`${r}:${n}`)}const v=await t.tagByDigest({sourceImage:r,digest:i,tags:g});return v.success?k({contentHashTagsByService:m}):l(v.error)}async function Ee(e,s,t,r){const o=e.dockerProvider;if(!o)return k({});const a=s.awsProvider.getAccountId();if(!a)return r.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),k({});const i=s.awsProvider.getRegion(),m=B(t.path,"cdk.out"),g=M(m),v=oe(a,i,t.appName);if(C.debug(P,"runDockerBuild starting",{appName:t.appName,appPath:t.path,accountId:a,region:i,cdkOutPath:m,manifestServiceCount:g.length,manifestServices:g.map(n=>({name:n.name,path:n.docker.path,context:n.docker.context,target:n.docker.target})),stepId:y.DOCKER_OPERATIONS,stepName:E}),r.onStepStart?.(y.DOCKER_OPERATIONS,E),g.length===0){r.onLog?.("Initialising ECR repository\u2026","info");const n=await o.initialiseECR({appName:t.appName,region:i,accountId:a});n.success||r.onLog?.(`ECR initialisation failed: ${f(n.error.message)}`,"warn"),r.onLog?.("Building and pushing Docker image\u2026","info");const c=await se(o,t,i,a,r);if(!c.success){C.debug(P,"Docker buildAndPush (legacy) failed",{appName:t.appName,error:f(c.error.message)}),r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const S=new Error(f(c.error.message));return r.onError?.(S),l(S)}return r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"completed"),r.onLog?.(`Docker image pushed: ${c.data.imageUri}`,"info"),k({})}const p=await o.preflight?.();if(p!==void 0&&!p.success){r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const n=new Error(f(p.error.message));return r.onError?.(n),l(n)}const h=ie(g,t.path,e.workingDirectory);r.onLog?.(`Building and pushing ${g.length} service image(s) in ${h.length} group(s)\u2026`,"info");let u=!1;if(o.beginEcrSession!==void 0){const n=await o.beginEcrSession({accountId:a,region:i});if(!n.success){r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const c=new Error(f(n.error.message));return r.onError?.(c),l(c)}u=!0}try{const n={};for(let c=0;c<h.length;c++){const S=h[c],R=S.members.map(b=>b.name).join(", ");r.onLog?.(`Building group ${c+1}/${h.length} (${R})\u2026`,"info");const A=await ae(S,o,t,i,a,v,r,s.awsProvider,e.abortSignal,e.options?.environment,e.options?.flagBuildArgs,e.options?.flagBuildSecrets);if(!A.success){C.debug(P,"Docker buildAndPush failed",{appName:t.appName,leader:S.members[0]?.name,members:R,error:f(A.error.message)}),r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const b=new Error(f(A.error.message));return r.onError?.(b),l(b)}for(const[b,w]of Object.entries(A.data.contentHashTagsByService))n[b]=w}return C.debug(P,"Docker buildAndPush succeeded",{appName:t.appName,services:g.map(c=>c.name),groupCount:h.length,contentHashTags:n}),r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"completed"),r.onLog?.(`Docker images pushed for ${g.length} service(s)`,"info"),k(n)}finally{u&&o.endEcrSession!==void 0&&await o.endEcrSession().catch(n=>{C.debug(P,"endEcrSession cleanup failed",{error:f(n instanceof Error?n.message:String(n))})})}}export{E as DOCKER_BUILD_STEP_NAME,Ee as runDockerBuild};
1
+ import{isAbsolute as K,join as $,dirname as H,resolve as x}from"node:path";import{existsSync as T}from"node:fs";import{success as k,failure as f}from"@fjall/generator";import{deriveContentHashTag as F,maskSensitiveOutput as m,toKebab as I}from"@fjall/util";import{evaluateBakeGuard as U,evaluateResolvedBuildArgValues as G,acknowledgedBuildArgKeys as V,BAKE_GUARD_EXPOSURE_CLAUSE as M}from"@fjall/util/docker";import{logger as P}from"@fjall/util/logger";import{parseDockerServicesFromManifest as W}from"@fjall/util/manifest";import{STEP_IDS as b}from"../../types/stepDefinitions.js";import{resolveBuildSecrets as z,resolveSecretRefValue as X,resolveBuildSecretSessionProvider as Y,sourcedRefsFromBuildArgs as q}from"./buildSecretResolver.js";import{partitionForRegion as J}from"./buildSecretSession.js";import{resolveBuildArgs as Q}from"./buildArgResolver.js";import{validateBuildGroup as Z}from"./buildGroupValidator.js";const C="dockerBuildHelper",E="Building and pushing Docker image";function ee(e,s,r){const t=$(s,e);if(T(t))return{path:t,base:s};if(r!==""&&r!==s){const o=$(r,e);if(T(o))return{path:o,base:r}}return{path:t,base:s}}function te(e,s,r){const t=e.docker.path,o=e.docker.context;let a,i;if(K(t))a=t,i=s;else{const c=ee(t,s,r);a=c.path,i=c.base}return{buildContext:o?K(o)?x(o):x(i,o):x(H(a)),dockerfilePath:a,target:e.docker.target,resolvedViaFallback:i!==s}}function re(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function ne(e,s){return`Dockerfile not found for service '${e}' at ${s}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function ie(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function oe(e,s,r){return`${e}.dkr.ecr.${s}.${ie(s)}/${I(r)}`}function se(e,s,r){const t=new Map;for(const o of e){const a=te(o,s,r),i=re(a),p=t.get(i);p?p.members.push(o):t.set(i,{identity:a,members:[o]})}return Array.from(t.values())}async function ae(e,s,r,t,o){const a=await e.buildAndPush({appName:s.appName,appPath:s.path,region:r,accountId:t},(i,p,c,v)=>{o.onDockerProgress?.(m(i),p,c,v)});return a.success?k({imageUri:a.data.imageUri,imageTag:a.data.imageTag}):f(a.error)}function L(){return{contentHashTagsByService:{},imageDigestsByService:{}}}async function de(e,s,r,t,o,a,i,p,c,v,y,h){const u=e.members[0];if(!T(e.identity.dockerfilePath))return f(new Error(ne(u.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&i.onLog?.(`Building service '${u.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const n=[...u.docker.buildSecrets??[],...h??[]],g=Z({serviceName:u.name,dockerfilePath:e.identity.dockerfilePath,buildContext:e.identity.buildContext,...e.identity.target!==void 0&&{target:e.identity.target},...u.docker.buildArgs!==void 0&&{buildArgs:u.docker.buildArgs},...n.length>0&&{buildSecrets:n},members:e.members.map(d=>({serviceName:d.name,...d.docker.buildArgs!==void 0&&{buildArgs:d.docker.buildArgs},...d.docker.buildSecrets!==void 0&&{buildSecrets:d.docker.buildSecrets}}))});if(!g.success)return f(g.error);const l=U(u.docker.buildArgs,I(r.appName));for(const d of l.warnings)i.onLog?.(m(d.message),"warn");if(l.findings.length>0)return f(new Error(m(l.findings.map(d=>d.message).join(" "))));const R=n,B=q(u.docker.buildArgs),A={appName:r.appName,region:t,accountId:o,partition:J(t)},S=await Y(p,[...R,...B],A);if(!S.success)return f(S.error);const D=S.data,N=await Q({manifestBuildArgs:u.docker.buildArgs,serviceDir:e.identity.buildContext,appDir:r.path,...v!==void 0&&{stage:v},shellEnv:process.env,...y!==void 0&&{flagBuildArgs:y},resolveSourcedValue:(d,j)=>X(D,{id:d,...j},c)});if(!N.success)return f(N.error);const O=N.data.buildArgs;for(const d of N.data.inferredDotenvKeys)i.onLog?.(m(`buildArg "${d}" was auto-declared from a .env file and will be baked into the image \u2014 ${M}.`),"warn");const _=G(O,I(r.appName),V(u.docker.buildArgs));if(_.length>0)return f(new Error(m(_.map(d=>d.message).join(" "))));let w;if(R.length>0){const d=await z(D,R,c);if(!d.success)return f(d.error);w=d.data}try{const d={appName:r.appName,appPath:e.identity.buildContext,region:t,accountId:o,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,pushByDigest:!0,serviceName:u.name,...e.identity.target!==void 0&&{target:e.identity.target},...Object.keys(O).length>0&&{buildArgs:O},...w!==void 0&&{buildSecrets:w.resolved}};return await ce(e,d,s,a,i)}finally{w!==void 0&&await w.cleanup().catch(()=>i.onLog?.("Build-secret cleanup failed.","warn"))}}async function ce(e,s,r,t,o){const a=await r.buildAndPush(s,(h,u,n,g)=>{o.onDockerProgress?.(m(h),u,n,g)});if(!a.success)return f(a.error);const i=a.data.imageDigest;if(typeof i!="string"||!i.startsWith("sha256:"))return f(new Error(`Buildx push succeeded but returned an invalid digest: ${m(String(i))}`));const p={},c={},v=[];for(const h of e.members){const u=h.docker.target,n=F(i,h.name,u);if(!n.success)return f(n.error);const g=n.data,l=h.name;p[l]=g,c[l]=i,v.push(`${t}:${g}`)}const y=await r.tagByDigest({sourceImage:t,digest:i,tags:v});return y.success?k({contentHashTagsByService:p,imageDigestsByService:c}):f(y.error)}async function Re(e,s,r,t){const o=e.dockerProvider;if(!o)return k(L());const a=s.awsProvider.getAccountId();if(!a)return t.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),k(L());const i=s.awsProvider.getRegion(),p=$(r.path,"cdk.out"),c=W(p),v=oe(a,i,r.appName);if(P.debug(C,"runDockerBuild starting",{appName:r.appName,appPath:r.path,accountId:a,region:i,cdkOutPath:p,manifestServiceCount:c.length,manifestServices:c.map(n=>({name:n.name,path:n.docker.path,context:n.docker.context,target:n.docker.target})),stepId:b.DOCKER_OPERATIONS,stepName:E}),t.onStepStart?.(b.DOCKER_OPERATIONS,E),c.length===0){t.onLog?.("Initialising ECR repository\u2026","info");const n=await o.initialiseECR({appName:r.appName,region:i,accountId:a});n.success||t.onLog?.(`ECR initialisation failed: ${m(n.error.message)}`,"warn"),t.onLog?.("Building and pushing Docker image\u2026","info");const g=await ae(o,r,i,a,t);if(!g.success){P.debug(C,"Docker buildAndPush (legacy) failed",{appName:r.appName,error:m(g.error.message)}),t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const l=new Error(m(g.error.message));return t.onError?.(l),f(l)}return t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"completed"),t.onLog?.(`Docker image pushed: ${g.data.imageUri}`,"info"),k(L())}const y=await o.preflight?.();if(y!==void 0&&!y.success){t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const n=new Error(m(y.error.message));return t.onError?.(n),f(n)}const h=se(c,r.path,e.workingDirectory);t.onLog?.(`Building and pushing ${c.length} service image(s) in ${h.length} group(s)\u2026`,"info");let u=!1;if(o.beginEcrSession!==void 0){const n=await o.beginEcrSession({accountId:a,region:i});if(!n.success){t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const g=new Error(m(n.error.message));return t.onError?.(g),f(g)}u=!0}try{const n={},g={};for(let l=0;l<h.length;l++){const R=h[l],B=R.members.map(S=>S.name).join(", ");t.onLog?.(`Building group ${l+1}/${h.length} (${B})\u2026`,"info");const A=await de(R,o,r,i,a,v,t,s.awsProvider,e.abortSignal,e.options?.environment,e.options?.flagBuildArgs,e.options?.flagBuildSecrets);if(!A.success){P.debug(C,"Docker buildAndPush failed",{appName:r.appName,leader:R.members[0]?.name,members:B,error:m(A.error.message)}),t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const S=new Error(m(A.error.message));return t.onError?.(S),f(S)}for(const[S,D]of Object.entries(A.data.contentHashTagsByService))n[S]=D;for(const[S,D]of Object.entries(A.data.imageDigestsByService))g[S]=D}return P.debug(C,"Docker buildAndPush succeeded",{appName:r.appName,services:c.map(l=>l.name),groupCount:h.length,contentHashTags:n}),t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"completed"),t.onLog?.(`Docker images pushed for ${c.length} service(s)`,"info"),k({contentHashTagsByService:n,imageDigestsByService:g,repoUri:v})}finally{u&&o.endEcrSession!==void 0&&await o.endEcrSession().catch(n=>{P.debug(C,"endEcrSession cleanup failed",{error:m(n instanceof Error?n.message:String(n))})})}}export{E as DOCKER_BUILD_STEP_NAME,L as emptyBuildProducts,Re as runDockerBuild};
@@ -1 +1 @@
1
- import{success as u,failure as y}from"@fjall/generator";import{isApplicationOperation as f}from"../types/operations.js";import{isAborted as w}from"../aws/organisations/types.js";import{checkRegionEnabled as b,regionDisabledMessage as A}from"../aws/regions/regionOptStatus.js";import{createDeployServices as k}from"./serviceFactory.js";import{resolveOperation as m}from"./resolveOperation.js";import{deployApplication as D}from"./application/applicationDeploy.js";import{deployOrganisation as v}from"./organisation/organisationDeploy.js";async function N(e){const s=Date.now(),l=await b(e.awsCredentials.region,e.awsCredentials,e.abortSignal);if(l==="disabled"){const i=new Error(A(e.awsCredentials.region));return e.callbacks.onError?.(i),y(i)}l==="enabling"&&e.callbacks.onProgress?.({type:"warning",message:`Region ${e.awsCredentials.region} is still activating (opt-in status ENABLING); the deploy may fail until activation completes.`});let r;const g=e.callbacks.onFailureAnalysis,a={...e,callbacks:{...e.callbacks,onFailureAnalysis:i=>{r=i,g?.(i)}}},c=k(a),o=await m(e.target,e.workingDirectory);if(!o.success)return e.callbacks.onError?.(o.error),o;const n=o.data,p=f(n)?"application":"organisation",t=f(n)?await D(a,c,n):await v(a,c,n);if(w(e.abortSignal))return u({target:e.target,deploymentType:p,cancelled:!0,durationMs:Date.now()-s});if(!t.success)return t;const d=r!==void 0&&t.data.failureAnalysis===void 0?{...t.data,failureAnalysis:r}:t.data;return u({...d,...d.durationMs===void 0?{durationMs:Date.now()-s}:{}})}export{N as deploy};
1
+ import{success as u,failure as y}from"@fjall/generator";import{isApplicationOperation as f}from"../types/operations.js";import{isAborted as w}from"../aws/organisations/types.js";import{checkRegionEnabled as b,regionDisabledMessage as A}from"../aws/regions/regionOptStatus.js";import{createDeployServices as k}from"./serviceFactory.js";import{resolveOperation as m}from"./resolveOperation.js";import{deployApplication as D}from"./application/applicationDeploy.js";import{deployOrganisation as v}from"./organisation/organisationDeploy.js";async function N(e){const s=Date.now(),l=await b(e.awsCredentials.region,e.awsCredentials,e.abortSignal);if(l==="disabled"){const i=new Error(A(e.awsCredentials.region));return e.callbacks.onError?.(i),y(i)}l==="enabling"&&e.callbacks.onProgress?.({type:"warning",message:`Region ${e.awsCredentials.region} is still activating (opt-in status ENABLING); the deploy may fail until activation completes.`});let r;const g=e.callbacks.onFailureAnalysis,a={...e,callbacks:{...e.callbacks,onFailureAnalysis:i=>{r=i,g?.(i)}}},c=k(a),o=await m(e.target,e.workingDirectory);if(!o.success)return e.callbacks.onError?.(o.error),o;const n=o.data,p=f(n)?"application":"organisation",t=f(n)?await D(a,c,n):await v(a,c,n);if(w(e.abortSignal))return u({target:e.target,deploymentType:p,cancelled:!0,artefacts:[],durationMs:Date.now()-s});if(!t.success)return t;const d=r!==void 0&&t.data.failureAnalysis===void 0?{...t.data,failureAnalysis:r}:t.data;return u({...d,...d.durationMs===void 0?{durationMs:Date.now()-s}:{}})}export{N as deploy};
@@ -104,8 +104,11 @@ export interface ECRInitResult {
104
104
  }
105
105
  /**
106
106
  * Parameters for tagging existing ECR images for ECS services.
107
- * Used for non-Dockerfile apps where CodeBuild pushes :latest but
108
- * ECS expects :<serviceName>-latest tags.
107
+ * Used on the non-Dockerfile welcome-image path: a create-once,
108
+ * identical-manifest copy of the seeded `:latest` image to
109
+ * `:<serviceName>-latest`, safe on IMMUTABLE repositories. Those tags are
110
+ * read only by stacks synthesised before 2026-05-13 — newer stacks default
111
+ * their ImageTag parameter to `:latest`.
109
112
  */
110
113
  export interface TagImagesParams {
111
114
  appName: string;
@@ -32,6 +32,7 @@ export type { AccountsConfig } from "./organisation/accountsConfig.js";
32
32
  export type { DockerProvider, DockerProgressCallback, DockerServiceConfig, DockerBuildParams, DockerBuildResult, ResolvedBuildSecret, ECRInitParams, ECRInitResult, TagImagesParams, TagImagesResult, TagByDigestParams } from "./dockerInterface.js";
33
33
  export { resolveBuildSecrets, resolveSecretRefValue, resolveBuildSecretSessionProvider, sourcedRefsFromBuildArgs, type ResolvedBuildSecrets } from "./application/buildSecretResolver.js";
34
34
  export { buildBuildSecretSessionPolicy, partitionForRegion, type BuildSecretRef, type BuildSecretSessionContext } from "./application/buildSecretSession.js";
35
+ export { buildDeploySessionPolicy, type DeploySessionContext } from "./application/deploySessionPolicy.js";
35
36
  export { resolveBuildArgs, type ResolveBuildArgsContext, type ResolveBuildArgsResult, type SourcedBuildArgResolver } from "./application/buildArgResolver.js";
36
37
  export { validateBuildGroup, type ValidateBuildGroupInput, type BuildGroupMemberInputs } from "./application/buildGroupValidator.js";
37
38
  export type { DomainDeployProvider, DomainConfig, DomainDeployResult } from "./domainInterface.js";
@@ -1 +1 @@
1
- import{deploy as r}from"./deploy.js";import{destroy as i}from"./destroy.js";import{deployOrganisation as s}from"./organisation/organisationDeploy.js";import{destroyOrganisation as c}from"./organisation/organisationDestroy.js";import{cleanupFailedStack as u,emptyS3Bucket as p,preEmptyStackBuckets as m,formatQuarantineSuspectedMessage as f,formatRetainedBucketsMessage as d,isQuarantineDetail as A,isRetainedBucketsDetail as T,PRE_EMPTY_TAG_KEYS as S,isCleanableState as x,SAFE_CLEANUP_STATES as P}from"./stackCleanup.js";import{partitionAccounts as g,buildRegionList as y,buildAccountRegionPairs as E,cascadeHomeRegion as R,cascadeOperationKey as B}from"./organisation/cascadeHelpers.js";import{projectScalarSummary as k,projectAccountRows as U}from"./organisation/cascadeSummary.js";import{reconcileProviderAccounts as C,mergeReconciledProviderAccounts as L}from"./organisation/reconcileProviderAccounts.js";import{decideNextTransition as N,reconcileTrailMigration as D,ORG_TRAIL_BUCKET_OUTPUT_KEY as b,TRAIL_BUCKET_OUTPUT_KEY as Y,TRAIL_KEY_ARN_OUTPUT_KEY as M}from"./trailMigration/trailMigration.js";import{decommissionMemberTrailStorage as G}from"./trailMigration/memberTrailCleanup.js";import{unlockBucket as Q}from"./unlock/unlockBucket.js";import{unlockQueue as H}from"./unlock/unlockQueue.js";import{triageBucketPolicy as j,isEnforceSslStatement as w}from"./unlock/bucketPolicyTriage.js";import{toResourcePolicyStatements as q}from"./unlock/toResourcePolicyStatements.js";import{restoreBucketPolicy as J,synthesiseEnforceSslDocument as X,ensureEnforceSsl as Z}from"./unlock/restoreBucketPolicy.js";import{restoreAndReconcileQuarantinedBucket as ee}from"./unlock/restoreAndReconcileQuarantinedBucket.js";import{parseAccountsConfiguration as re,flattenAccountsToEnvironments as te,extractAllAccountNames as ie,accountsConfigToOUTree as ne,isStringArray as se,isAccountsConfig as ae,isOuOnlyAccountBucket as ce,OU_ONLY_ACCOUNT_BUCKETS as le}from"./organisation/accountsConfig.js";import{resolveBuildSecrets as pe,resolveSecretRefValue as me,resolveBuildSecretSessionProvider as fe,sourcedRefsFromBuildArgs as de}from"./application/buildSecretResolver.js";import{buildBuildSecretSessionPolicy as Te,partitionForRegion as Se}from"./application/buildSecretSession.js";import{resolveBuildArgs as Pe}from"./application/buildArgResolver.js";import{validateBuildGroup as ge}from"./application/buildGroupValidator.js";import{runOpenNextBuild as Ee}from"./application/openNextBuild.js";import{runOrganisationSetup as Be,ORG_SETUP_PHASES as Oe}from"./organisation/organisationSetup.js";export*from"./builders/index.js";import{buildDeployPlan as ve,computeDeployPlan as Ce,computeAssemblyDigest as Le,digestHead as Ke,classifyImpact as Ne,derivePropertyChanges as De,isDataLoss as be,isStatefulResourceType as Ye,renderPlanSummary as Me,renderPlanLines as Fe,toWirePlanChanges as Ge,signApprovalToken as Ie,verifyApprovalToken as Qe,DEFAULT_APPROVAL_TTL_MS as he,APPROVAL_TOKEN_PATTERN as He}from"./application/plan/index.js";import{runApprovalGate as je}from"./application/approvalGate.js";export{He as APPROVAL_TOKEN_PATTERN,he as DEFAULT_APPROVAL_TTL_MS,Oe as ORG_SETUP_PHASES,b as ORG_TRAIL_BUCKET_OUTPUT_KEY,le as OU_ONLY_ACCOUNT_BUCKETS,S as PRE_EMPTY_TAG_KEYS,P as SAFE_CLEANUP_STATES,Y as TRAIL_BUCKET_OUTPUT_KEY,M as TRAIL_KEY_ARN_OUTPUT_KEY,ne as accountsConfigToOUTree,E as buildAccountRegionPairs,Te as buildBuildSecretSessionPolicy,ve as buildDeployPlan,y as buildRegionList,R as cascadeHomeRegion,B as cascadeOperationKey,Ne as classifyImpact,u as cleanupFailedStack,Le as computeAssemblyDigest,Ce as computeDeployPlan,N as decideNextTransition,G as decommissionMemberTrailStorage,r as deploy,s as deployOrganisation,De as derivePropertyChanges,i as destroy,c as destroyOrganisation,Ke as digestHead,p as emptyS3Bucket,Z as ensureEnforceSsl,ie as extractAllAccountNames,te as flattenAccountsToEnvironments,f as formatQuarantineSuspectedMessage,d as formatRetainedBucketsMessage,ae as isAccountsConfig,x as isCleanableState,be as isDataLoss,w as isEnforceSslStatement,ce as isOuOnlyAccountBucket,A as isQuarantineDetail,T as isRetainedBucketsDetail,Ye as isStatefulResourceType,se as isStringArray,L as mergeReconciledProviderAccounts,re as parseAccountsConfiguration,g as partitionAccounts,Se as partitionForRegion,m as preEmptyStackBuckets,U as projectAccountRows,k as projectScalarSummary,C as reconcileProviderAccounts,D as reconcileTrailMigration,Fe as renderPlanLines,Me as renderPlanSummary,Pe as resolveBuildArgs,fe as resolveBuildSecretSessionProvider,pe as resolveBuildSecrets,me as resolveSecretRefValue,ee as restoreAndReconcileQuarantinedBucket,J as restoreBucketPolicy,je as runApprovalGate,Ee as runOpenNextBuild,Be as runOrganisationSetup,Ie as signApprovalToken,de as sourcedRefsFromBuildArgs,X as synthesiseEnforceSslDocument,q as toResourcePolicyStatements,Ge as toWirePlanChanges,j as triageBucketPolicy,Q as unlockBucket,H as unlockQueue,ge as validateBuildGroup,Qe as verifyApprovalToken};
1
+ import{deploy as r}from"./deploy.js";import{destroy as i}from"./destroy.js";import{deployOrganisation as s}from"./organisation/organisationDeploy.js";import{destroyOrganisation as a}from"./organisation/organisationDestroy.js";import{cleanupFailedStack as u,emptyS3Bucket as p,preEmptyStackBuckets as m,formatQuarantineSuspectedMessage as f,formatRetainedBucketsMessage as d,isQuarantineDetail as A,isRetainedBucketsDetail as S,PRE_EMPTY_TAG_KEYS as T,isCleanableState as x,SAFE_CLEANUP_STATES as P}from"./stackCleanup.js";import{partitionAccounts as y,buildRegionList as g,buildAccountRegionPairs as E,cascadeHomeRegion as R,cascadeOperationKey as B}from"./organisation/cascadeHelpers.js";import{projectScalarSummary as k,projectAccountRows as U}from"./organisation/cascadeSummary.js";import{reconcileProviderAccounts as C,mergeReconciledProviderAccounts as L}from"./organisation/reconcileProviderAccounts.js";import{decideNextTransition as D,reconcileTrailMigration as N,ORG_TRAIL_BUCKET_OUTPUT_KEY as b,TRAIL_BUCKET_OUTPUT_KEY as Y,TRAIL_KEY_ARN_OUTPUT_KEY as M}from"./trailMigration/trailMigration.js";import{decommissionMemberTrailStorage as G}from"./trailMigration/memberTrailCleanup.js";import{unlockBucket as Q}from"./unlock/unlockBucket.js";import{unlockQueue as H}from"./unlock/unlockQueue.js";import{triageBucketPolicy as j,isEnforceSslStatement as w}from"./unlock/bucketPolicyTriage.js";import{toResourcePolicyStatements as q}from"./unlock/toResourcePolicyStatements.js";import{restoreBucketPolicy as J,synthesiseEnforceSslDocument as X,ensureEnforceSsl as Z}from"./unlock/restoreBucketPolicy.js";import{restoreAndReconcileQuarantinedBucket as ee}from"./unlock/restoreAndReconcileQuarantinedBucket.js";import{parseAccountsConfiguration as re,flattenAccountsToEnvironments as te,extractAllAccountNames as ie,accountsConfigToOUTree as ne,isStringArray as se,isAccountsConfig as ce,isOuOnlyAccountBucket as ae,OU_ONLY_ACCOUNT_BUCKETS as le}from"./organisation/accountsConfig.js";import{resolveBuildSecrets as pe,resolveSecretRefValue as me,resolveBuildSecretSessionProvider as fe,sourcedRefsFromBuildArgs as de}from"./application/buildSecretResolver.js";import{buildBuildSecretSessionPolicy as Se,partitionForRegion as Te}from"./application/buildSecretSession.js";import{buildDeploySessionPolicy as Pe}from"./application/deploySessionPolicy.js";import{resolveBuildArgs as ye}from"./application/buildArgResolver.js";import{validateBuildGroup as Ee}from"./application/buildGroupValidator.js";import{runOpenNextBuild as Be}from"./application/openNextBuild.js";import{runOrganisationSetup as ke,ORG_SETUP_PHASES as Ue}from"./organisation/organisationSetup.js";export*from"./builders/index.js";import{buildDeployPlan as Le,computeDeployPlan as Ke,computeAssemblyDigest as De,digestHead as Ne,classifyImpact as be,derivePropertyChanges as Ye,isDataLoss as Me,isStatefulResourceType as Fe,renderPlanSummary as Ge,renderPlanLines as Ie,toWirePlanChanges as Qe,signApprovalToken as he,verifyApprovalToken as He,DEFAULT_APPROVAL_TTL_MS as Ve,APPROVAL_TOKEN_PATTERN as je}from"./application/plan/index.js";import{runApprovalGate as We}from"./application/approvalGate.js";export{je as APPROVAL_TOKEN_PATTERN,Ve as DEFAULT_APPROVAL_TTL_MS,Ue as ORG_SETUP_PHASES,b as ORG_TRAIL_BUCKET_OUTPUT_KEY,le as OU_ONLY_ACCOUNT_BUCKETS,T as PRE_EMPTY_TAG_KEYS,P as SAFE_CLEANUP_STATES,Y as TRAIL_BUCKET_OUTPUT_KEY,M as TRAIL_KEY_ARN_OUTPUT_KEY,ne as accountsConfigToOUTree,E as buildAccountRegionPairs,Se as buildBuildSecretSessionPolicy,Le as buildDeployPlan,Pe as buildDeploySessionPolicy,g as buildRegionList,R as cascadeHomeRegion,B as cascadeOperationKey,be as classifyImpact,u as cleanupFailedStack,De as computeAssemblyDigest,Ke as computeDeployPlan,D as decideNextTransition,G as decommissionMemberTrailStorage,r as deploy,s as deployOrganisation,Ye as derivePropertyChanges,i as destroy,a as destroyOrganisation,Ne as digestHead,p as emptyS3Bucket,Z as ensureEnforceSsl,ie as extractAllAccountNames,te as flattenAccountsToEnvironments,f as formatQuarantineSuspectedMessage,d as formatRetainedBucketsMessage,ce as isAccountsConfig,x as isCleanableState,Me as isDataLoss,w as isEnforceSslStatement,ae as isOuOnlyAccountBucket,A as isQuarantineDetail,S as isRetainedBucketsDetail,Fe as isStatefulResourceType,se as isStringArray,L as mergeReconciledProviderAccounts,re as parseAccountsConfiguration,y as partitionAccounts,Te as partitionForRegion,m as preEmptyStackBuckets,U as projectAccountRows,k as projectScalarSummary,C as reconcileProviderAccounts,N as reconcileTrailMigration,Ie as renderPlanLines,Ge as renderPlanSummary,ye as resolveBuildArgs,fe as resolveBuildSecretSessionProvider,pe as resolveBuildSecrets,me as resolveSecretRefValue,ee as restoreAndReconcileQuarantinedBucket,J as restoreBucketPolicy,We as runApprovalGate,Be as runOpenNextBuild,ke as runOrganisationSetup,he as signApprovalToken,de as sourcedRefsFromBuildArgs,X as synthesiseEnforceSslDocument,q as toResourcePolicyStatements,Qe as toWirePlanChanges,j as triageBucketPolicy,Q as unlockBucket,H as unlockQueue,Ee as validateBuildGroup,He as verifyApprovalToken};
@@ -2,4 +2,4 @@ import{join as W}from"node:path";import{success as _,failure as Y}from"@fjall/ge
2
2
  `);e.onLog?.(l(`Cascade failed for ${i.length} target(s):
3
3
  ${r}`),"warn")}}if(i.length>0){const o=i.map(a=>l(`${a.accountId}: ${a.error}`)).join(`
4
4
  `),r=new Error(`Organisation root deployed, but the cascade failed for ${i.length} target(s):
5
- ${o}`);return e.onError?.(r),Y(r)}return _({target:O.target,deploymentType:"organisation",outputs:w,...y.length>0?{cascadeOutputs:y}:{},...T?{}:{noChanges:!0},durationMs:Date.now()-H})}export{ge as deployOrgWithCascade};
5
+ ${o}`);return e.onError?.(r),Y(r)}return _({target:O.target,deploymentType:"organisation",outputs:w,artefacts:[],...y.length>0?{cascadeOutputs:y}:{},...T?{}:{noChanges:!0},durationMs:Date.now()-H})}export{ge as deployOrgWithCascade};
@@ -1 +1 @@
1
- import{success as N}from"@fjall/generator";import{BackupClient as w}from"@aws-sdk/client-backup";import{IAMClient as D}from"@aws-sdk/client-iam";import{getOrganisationStackName as I}from"../../types/operations.js";import{accountHasDisasterRecovery as k,describeBackupVaultExists as y}from"../../aws/organisations/backup.js";import{describeAccountGlobalsExist as L,buildMissingAccountGlobalsAdvisory as T}from"../../aws/organisations/accountGlobals.js";import{targetsNonPrimaryRegion as h,synthOrFail as v,bootstrapOrFail as F,forwardOutput as M,forwardResourceProgress as G}from"../contextHelpers.js";import{accountTier as S}from"@fjall/util";import{buildOrgContext as Y,resolveOrgDetails as x,resolveOrgDetailsForSolo as B}from"./orgContext.js";import{hasOrganisationTierAccount as V}from"../../aws/organisations/accountGlobals.js";import{INFRA_STEPS as o,INFRA_STEP_TOTAL as i,failPrepareStep as c,maskAndFail as d,readStackOutputsBestEffort as _}from"./infraSteps.js";async function ot(n,r,a,s,E){const{callbacks:t}=n;t.onStepComplete?.(o.CONNECT.id,o.CONNECT.name,"completed",0,i),t.onStepStart?.(o.PREPARE.id,o.PREPARE.name,1,i);const R=r.awsProvider.getRegion(),l=n.orgConfig?.primaryRegion;if(l!==void 0&&h(R,l)){const e=await L(r.awsProvider.getClient(D),n.abortSignal);if(!e.success)return c(t),d(t,e.error.message);if(!e.data)return c(t),d(t,T({target:a.target,deployRegion:R,primaryRegion:l,...n.orgConfig!==void 0?{providerAccounts:n.orgConfig.providerAccounts}:{}}))}let f;if(V(n.orgConfig?.providerAccounts)){const e=await x(r,n.abortSignal);if(!e.success)return c(t),d(t,e.error.message);f=e.data}else f=await B(r,n.abortSignal);const u=s==="account"?n.orgConfig?.providerAccounts.find(e=>e.name===a.target):n.orgConfig?.providerAccounts.find(e=>S(e)==="platform"),A=s==="account"&&u!==void 0&&S(u)==="organisation",g=Y(n,r,a,s,f,s==="account"?a.target:void 0,u?.trailLifecycle,A),m=n.orgConfig?.disasterRecoveryRegion;if(s==="account"&&(m!==void 0&&m!=="")&&(u===void 0||k(u.environment,m))){const e=await y(r.awsProvider.getClient(w),n.abortSignal);if(!e.success)return c(t),d(t,e.error.message);g.fjallAdoptBackupVault=e.data}t.onLog?.(`Synthesising ${s} infrastructure\u2026`,"info");const p=await v(r,g,t,"CDK synthesis failed");if(!p.success)return c(t),p;const O=await F(r,g,t);if(!O.success)return c(t),O;t.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"completed",1,i);const P=I(a.type);t.onStepStart?.(o.DEPLOY.id,o.DEPLOY.name,2,i);const C=await r.cdkService.runCdkDeploy(g,P,M(t),G(t),r.awsProvider);if(!C.success)return t.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"error",2,i),d(t,C.error);t.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"completed",2,i);const b=await _(r,t,P,"Failed to read stack outputs (non-critical)");return t.onStepStart?.(o.MONITORING.id,o.MONITORING.name,3,i),t.onStepComplete?.(o.MONITORING.id,o.MONITORING.name,"completed",3,i),N({target:a.target,deploymentType:"organisation",outputs:b,durationMs:Date.now()-E})}export{ot as deploySingleComponent};
1
+ import{success as N}from"@fjall/generator";import{BackupClient as w}from"@aws-sdk/client-backup";import{IAMClient as D}from"@aws-sdk/client-iam";import{getOrganisationStackName as I}from"../../types/operations.js";import{accountHasDisasterRecovery as k,describeBackupVaultExists as y}from"../../aws/organisations/backup.js";import{describeAccountGlobalsExist as L,buildMissingAccountGlobalsAdvisory as T}from"../../aws/organisations/accountGlobals.js";import{targetsNonPrimaryRegion as h,synthOrFail as v,bootstrapOrFail as F,forwardOutput as M,forwardResourceProgress as G}from"../contextHelpers.js";import{accountTier as S}from"@fjall/util";import{buildOrgContext as Y,resolveOrgDetails as x,resolveOrgDetailsForSolo as B}from"./orgContext.js";import{hasOrganisationTierAccount as V}from"../../aws/organisations/accountGlobals.js";import{INFRA_STEPS as o,INFRA_STEP_TOTAL as i,failPrepareStep as c,maskAndFail as d,readStackOutputsBestEffort as _}from"./infraSteps.js";async function ot(r,n,a,s,E){const{callbacks:t}=r;t.onStepComplete?.(o.CONNECT.id,o.CONNECT.name,"completed",0,i),t.onStepStart?.(o.PREPARE.id,o.PREPARE.name,1,i);const R=n.awsProvider.getRegion(),l=r.orgConfig?.primaryRegion;if(l!==void 0&&h(R,l)){const e=await L(n.awsProvider.getClient(D),r.abortSignal);if(!e.success)return c(t),d(t,e.error.message);if(!e.data)return c(t),d(t,T({target:a.target,deployRegion:R,primaryRegion:l,...r.orgConfig!==void 0?{providerAccounts:r.orgConfig.providerAccounts}:{}}))}let f;if(V(r.orgConfig?.providerAccounts)){const e=await x(n,r.abortSignal);if(!e.success)return c(t),d(t,e.error.message);f=e.data}else f=await B(n,r.abortSignal);const u=s==="account"?r.orgConfig?.providerAccounts.find(e=>e.name===a.target):r.orgConfig?.providerAccounts.find(e=>S(e)==="platform"),A=s==="account"&&u!==void 0&&S(u)==="organisation",g=Y(r,n,a,s,f,s==="account"?a.target:void 0,u?.trailLifecycle,A),m=r.orgConfig?.disasterRecoveryRegion;if(s==="account"&&(m!==void 0&&m!=="")&&(u===void 0||k(u.environment,m))){const e=await y(n.awsProvider.getClient(w),r.abortSignal);if(!e.success)return c(t),d(t,e.error.message);g.fjallAdoptBackupVault=e.data}t.onLog?.(`Synthesising ${s} infrastructure\u2026`,"info");const p=await v(n,g,t,"CDK synthesis failed");if(!p.success)return c(t),p;const O=await F(n,g,t);if(!O.success)return c(t),O;t.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"completed",1,i);const P=I(a.type);t.onStepStart?.(o.DEPLOY.id,o.DEPLOY.name,2,i);const C=await n.cdkService.runCdkDeploy(g,P,M(t),G(t),n.awsProvider);if(!C.success)return t.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"error",2,i),d(t,C.error);t.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"completed",2,i);const b=await _(n,t,P,"Failed to read stack outputs (non-critical)");return t.onStepStart?.(o.MONITORING.id,o.MONITORING.name,3,i),t.onStepComplete?.(o.MONITORING.id,o.MONITORING.name,"completed",3,i),N({target:a.target,deploymentType:"organisation",outputs:b,artefacts:[],durationMs:Date.now()-E})}export{ot as deploySingleComponent};
@@ -1,2 +1,2 @@
1
- import{tmpdir as l}from"os";import{join as C}from"path";import{logger as m}from"@fjall/util/logger";import{success as d,failure as i}from"@fjall/generator";import{CdkError as f}from"../../types/errors/CdkError.js";import{DEFAULT_REGION as O}from"../../aws/utils/regions.js";import{CdkArgumentBuilder as E}from"./CdkArgumentBuilder.js";import{isCdkError as A,formatInfrastructureError as c}from"./CdkErrorFormatter.js";import{hasCdkDifferences as P,parseDiffOutput as _}from"./CdkOutputParser.js";import{DEFAULT_DEPLOY_TIMEOUT_MS as h}from"./CdkEventMonitoring.js";import{analyseBootstrapError as g}from"./CdkOutputAnalyser.js";const R=3e5,L=18e4,k=12e5,y="cdk.out",u=Object.freeze({APP:"--app",CI:"--ci",REQUIRE_APPROVAL:"--require-approval",APPROVAL_NEVER:"never",VERBOSE:"--verbose",NO_LOOKUPS:"--no-lookups",ALL:"--all"});class K{processManager;argumentBuilder=new E;constructor(o){this.processManager=o}dispose(){this.processManager.dispose()}async checkDifferences(o,t,r){const e=["diff"];t?e.push(t):e.push(u.ALL),e.push("--no-color"),r?.noLookups&&e.push(u.NO_LOOKUPS);const s=await this.processManager.runCdkCommand(o,e,{...r,ignoreExitCode:!0,combineOutput:!0});if(!s.success)return i(new f(c(s.error,o),"diff_failed",void 0,void 0,s.error,void 0,!1));const a=s.data.output||"";if(A(a))return i(new f(c(a,o),"diff_failed",void 0,void 0,a,void 0,!1));const n=P(a),p=_(a);return d({hasDifferences:n,output:a,details:p})}async deploy(o,t,r){const e=["deploy"];r?.appDir?e.push(u.APP,r.appDir):r?.useCdkOut&&e.push(u.APP,"cdk.out"),t?e.push(t):e.push(u.ALL),e.push(u.REQUIRE_APPROVAL,u.APPROVAL_NEVER),e.push(u.CI),e.push("--no-version-reporting"),e.push("--no-path-metadata"),e.push("--no-asset-metadata"),r?.verbose?e.push(u.VERBOSE):e.push("--progress","events"),e.push(...this.argumentBuilder.buildParameterArgs(r?.parameters,t));const s={...r,timeout:r?.timeout||h};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(o,e,s):this.processManager.runCdkCommand(o,e,s)}async destroy(o,t,r){const e=["destroy"];r?.appDir?e.push(u.APP,r.appDir):r?.useCdkOut&&e.push(u.APP,"cdk.out"),t?e.push(t):e.push(u.ALL),e.push("--force"),r?.verbose&&e.push(u.VERBOSE);const s={...r,timeout:r?.timeout||h};return this.processManager.runCdkCommand(o,e,s)}async runImport(o,t,r){const e=["import"];r?.stacks&&r.stacks.length>0&&e.push(...r.stacks),t&&e.push("--resource-mapping",t),e.push(u.REQUIRE_APPROVAL,u.APPROVAL_NEVER),e.push(u.CI),r?.noLookups&&e.push(u.NO_LOOKUPS),r?.verbose&&(e.push(u.VERBOSE),e.push("--trace"),e.push("--debug"));const s={...r,timeout:r?.timeout||k};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(o,e,s):this.processManager.runCdkCommand(o,e,s)}async synth(o,t){t?.outputCallback?.(`Synthesising CloudFormation template...
2
- `);const r=t?.noLookups?[u.NO_LOOKUPS]:[],s=["--output",t?.outputDir??C(o,y)],a=t?.context?.region||O,n=await this.processManager.runCdkCommand(o,["synth",...r,...s,u.CI,"--quiet"],{...t,context:{...t?.context,region:a},timeout:t?.timeout||R});if(!n.success){const p=n.error?c(n.error,o):"Failed to synthesise CloudFormation template";return i(p)}return n}async bootstrap(o,t,r){const e=l();m.debug("CdkService","Starting CDK bootstrap",{accountId:o,region:t,bootstrapPath:e,target:`aws://${o}/${t}`});const s=await this.processManager.runCdkCommand(e,["bootstrap",`aws://${o}/${t}`,"--cloudformation-execution-policies","arn:aws:iam::aws:policy/AdministratorAccess","-c",`accountId=${o}`,u.REQUIRE_APPROVAL,u.APPROVAL_NEVER,u.CI,"--quiet","--force"],{...r,timeout:r?.timeout||L,context:{region:t,accountId:o},credentials:r?.credentials,skipProjectCheck:!0,extraEnv:{TERM:"dumb",CDK_DISABLE_NOTICES:"true",CDK_DISABLE_PROGRESS_BAR:"true"}});return m.debug("CdkService",s.success?"Bootstrap completed successfully":"Bootstrap exited with non-zero code",{accountId:o,region:t,exitCode:s.success?s.data.exitCode:void 0,output:s.success&&s.data.output?.trim()||"(empty)",error:s.success?"(empty)":s.error.trim()||"(empty)"}),s.success?d({output:"AWS environment bootstrapped",exitCode:0}):s.error.includes("already bootstrapped")?d({output:"Environment is already bootstrapped",exitCode:0}):i(g(s.error))}}export{L as BOOTSTRAP_TIMEOUT_MS,K as CdkCommandRunner,k as IMPORT_TIMEOUT_MS,R as SYNTH_TIMEOUT_MS};
1
+ import{tmpdir as l}from"os";import{join as C}from"path";import{logger as m}from"@fjall/util/logger";import{success as p,failure as i}from"@fjall/generator";import{CdkError as f}from"../../types/errors/CdkError.js";import{DEFAULT_REGION as O}from"../../aws/utils/regions.js";import{CdkArgumentBuilder as E}from"./CdkArgumentBuilder.js";import{isCdkError as A,formatInfrastructureError as c}from"./CdkErrorFormatter.js";import{hasCdkDifferences as P,parseDiffOutput as _}from"./CdkOutputParser.js";import{DEFAULT_DEPLOY_TIMEOUT_MS as h}from"./CdkEventMonitoring.js";import{analyseBootstrapError as g}from"./CdkOutputAnalyser.js";const R=3e5,y=18e4,L=12e5,k="cdk.out",o=Object.freeze({APP:"--app",CI:"--ci",REQUIRE_APPROVAL:"--require-approval",APPROVAL_NEVER:"never",VERBOSE:"--verbose",NO_LOOKUPS:"--no-lookups",ALL:"--all"});class K{processManager;argumentBuilder=new E;constructor(u){this.processManager=u}dispose(){this.processManager.dispose()}async checkDifferences(u,s,r){const e=["diff"];s?e.push(s):e.push(o.ALL),e.push("--no-color"),r?.noLookups&&e.push(o.NO_LOOKUPS);const t=await this.processManager.runCdkCommand(u,e,{...r,ignoreExitCode:!0,combineOutput:!0});if(!t.success)return i(new f(c(t.error,u),"diff_failed",void 0,void 0,t.error,void 0,!1));const n=t.data.output||"";if(A(n))return i(new f(c(n,u),"diff_failed",void 0,void 0,n,void 0,!1));const a=P(n),d=_(n);return p({hasDifferences:a,output:n,details:d})}async deploy(u,s,r){const e=["deploy"];r?.appDir?e.push(o.APP,r.appDir):r?.useCdkOut&&e.push(o.APP,"cdk.out"),s?e.push(s):e.push(o.ALL),e.push(o.REQUIRE_APPROVAL,o.APPROVAL_NEVER),e.push(o.CI),e.push("--no-version-reporting"),e.push("--no-path-metadata"),e.push("--no-asset-metadata"),r?.verbose?e.push(o.VERBOSE):e.push("--progress","events"),e.push(...this.argumentBuilder.buildParameterArgs(r?.parameters,s));const t={...r,timeout:r?.timeout||h};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(u,e,t):this.processManager.runCdkCommand(u,e,t)}async destroy(u,s,r){const e=["destroy"];r?.appDir?e.push(o.APP,r.appDir):r?.useCdkOut&&e.push(o.APP,"cdk.out"),s?e.push(s):e.push(o.ALL),e.push("--force"),r?.verbose&&e.push(o.VERBOSE);const t={...r,timeout:r?.timeout||h};return this.processManager.runCdkCommand(u,e,t)}async runImport(u,s,r){const e=["import"];r?.stacks&&r.stacks.length>0&&e.push(...r.stacks),s&&e.push("--resource-mapping",s),e.push(o.REQUIRE_APPROVAL,o.APPROVAL_NEVER),e.push(o.CI),r?.noLookups&&e.push(o.NO_LOOKUPS),r?.verbose&&(e.push(o.VERBOSE),e.push("--trace"),e.push("--debug"));const t={...r,timeout:r?.timeout||L};return r?.passThroughCDK?this.processManager.runCdkCommandPassthrough(u,e,t):this.processManager.runCdkCommand(u,e,t)}async synth(u,s){s?.outputCallback?.(`Synthesising CloudFormation template...
2
+ `);const r=s?.noLookups?[o.NO_LOOKUPS]:[],t=["--output",s?.outputDir??C(u,k)],n=s?.context?.region||O,a=await this.processManager.runCdkCommand(u,["synth",...r,...t,o.CI,"--quiet"],{...s,context:{...s?.context,region:n},timeout:s?.timeout||R});if(!a.success){const d=a.error?c(a.error,u):"Failed to synthesise CloudFormation template";return i(d)}return a}async bootstrap(u,s,r){const e=l();m.debug("CdkService","Starting CDK bootstrap",{accountId:u,region:s,bootstrapPath:e,target:`aws://${u}/${s}`});const t=await this.processManager.runCdkCommand(e,["bootstrap",`aws://${u}/${s}`,"--cloudformation-execution-policies","arn:aws:iam::aws:policy/AdministratorAccess",...r?.permissionsBoundary!==void 0&&r.permissionsBoundary!==""?["--custom-permissions-boundary",r.permissionsBoundary]:[],"-c",`accountId=${u}`,o.REQUIRE_APPROVAL,o.APPROVAL_NEVER,o.CI,"--quiet","--force"],{...r,timeout:r?.timeout||y,context:{region:s,accountId:u},credentials:r?.credentials,skipProjectCheck:!0,extraEnv:{TERM:"dumb",CDK_DISABLE_NOTICES:"true",CDK_DISABLE_PROGRESS_BAR:"true"}});return m.debug("CdkService",t.success?"Bootstrap completed successfully":"Bootstrap exited with non-zero code",{accountId:u,region:s,exitCode:t.success?t.data.exitCode:void 0,output:t.success&&t.data.output?.trim()||"(empty)",error:t.success?"(empty)":t.error.trim()||"(empty)"}),t.success?p({output:"AWS environment bootstrapped",exitCode:0}):t.error.includes("already bootstrapped")?p({output:"Environment is already bootstrapped",exitCode:0}):i(g(t.error))}}export{y as BOOTSTRAP_TIMEOUT_MS,K as CdkCommandRunner,L as IMPORT_TIMEOUT_MS,R as SYNTH_TIMEOUT_MS};
@@ -20,7 +20,7 @@ export declare class CdkService {
20
20
  synth(path: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
21
21
  bootstrap(accountId: string, region: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
22
22
  runCdkSynth(context: DeploymentContext, onOutput?: (chunk: string) => void, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
23
- runCdkBootstrap(context: DeploymentContext, onOutput?: (chunk: string) => void, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
23
+ runCdkBootstrap(context: DeploymentContext, onOutput?: (chunk: string) => void, credentials?: CdkOptions["credentials"], permissionsBoundary?: string): Promise<Result<StepOutput, string>>;
24
24
  runCdkDiff(context: DeploymentContext, onOutput?: (chunk: string) => void): Promise<Result<StepOutput, string>>;
25
25
  runCdkDeploy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, credentials?: CdkOptions["credentials"], parameters?: Record<string, string>): Promise<Result<StepOutput, string>>;
26
26
  runCdkDestroy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, useCdkOut?: boolean, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
@@ -1,3 +1,3 @@
1
- import{existsSync as F}from"fs";import{join as D}from"path";import{logger as S}from"@fjall/util/logger";import{success as C,failure as a}from"@fjall/generator";import{DEFAULT_REGION as h}from"../../aws/utils/regions.js";import{getErrorMessage as k,maskSensitiveOutput as p}from"@fjall/util";import{CdkEventMonitor as R,startStackMonitoring as w}from"./CdkEventMonitoring.js";import{analyseDeployOutput as E,analyseDestroyResult as K,createEnhancedOutputCallback as N}from"./CdkOutputAnalyser.js";import{CdkCommandRunner as O}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as T}from"./CdkArgumentBuilder.js";import{CdkProcessManager as L}from"./CdkProcessManager.js";import{cancelInFlightStack as P}from"./cancelInFlightStack.js";import{isAborted as v}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as W}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as $,resolveStackName as M,getFallbackStackName as _,buildDeploymentCdkContext as b}from"./cdkServiceHelpers.js";class te{commandRunner;eventMonitor;abortSignal;constructor(e){const r=e?.processManager??new L(new T,e?.abortSignal);this.commandRunner=new O(r),this.eventMonitor=new R({eventLogWriterFactory:e?.eventLogWriterFactory,...e?.onFailureAnalysis!==void 0?{onFailureAnalysis:e.onFailureAnalysis}:{}}),this.abortSignal=e?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(e,r,t){return this.commandRunner.checkDifferences(e,r,t)}async deploy(e,r,t){return this.commandRunner.deploy(e,r,t)}async destroy(e,r,t){return this.commandRunner.destroy(e,r,t)}async runImport(e,r,t){return this.commandRunner.runImport(e,r,t)}async synth(e,r){return this.commandRunner.synth(e,r)}async bootstrap(e,r,t){return this.commandRunner.bootstrap(e,r,t)}async runCdkSynth(e,r,t){const n=e.callerIdentity?.Account;try{const o=await this.synth(e.path,{outputCallback:r,context:b(e,n,e.region||h),...e.assemblyDir!==void 0?{outputDir:e.assemblyDir}:{},...t!==void 0?{credentials:t}:{}});return o.success?C({message:"CloudFormation template synthesised",details:o.data.output?{synthesisTime:o.data.output}:void 0}):a(o.error||"Failed to synthesise CloudFormation template")}catch(o){return a(`CDK synth failed: ${p(k(o))}`)}}async runCdkBootstrap(e,r,t){const n=e.callerIdentity?.Account,o=e.region||h;try{if(!n)return a("No AWS account ID available");const u=D(e.path,"node_modules");if(!F(u))return a(`Dependencies not installed. Please run 'npm install' in ${e.path} before deploying.`);const l=await this.bootstrap(n,o,{outputCallback:r,credentials:t});return l.success?C({message:"AWS environment bootstrapped"}):a(l.error||"Failed to bootstrap AWS environment")}catch(u){return a(`CDK bootstrap failed: ${p(k(u))}`)}}async runCdkDiff(e,r){const t=e.callerIdentity?.Account;try{const n=await this.checkDifferences(e.path,void 0,{verbose:e.options?.verbose,outputCallback:r,context:b(e,t,e.region||h)});return n.success?C({message:"Diff check complete",details:{hasDifferences:n.data.hasDifferences,details:n.data.details}}):a(`CDK diff failed: ${n.error.message}`)}catch(n){return a(`CDK diff failed: ${p(k(n))}`)}}async runCdkDeploy(e,r,t,n,o,u,l){const y=e.callerIdentity?.Account,f=e.region||h;if(!y)return a("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!o)return a("AwsProvider is required for deployment monitoring.");const m=e.assemblyDir??D(e.path,"cdk.out"),d=W(m,n);let c=null,g;try{const i=M(r,e)??_(e);c=await this.eventMonitor.createEventMonitor("deploy",i,f,e,o),t&&(t(p(`Starting CloudFormation deployment of ${i}...
1
+ import{existsSync as F}from"fs";import{join as D}from"path";import{logger as S}from"@fjall/util/logger";import{success as C,failure as a}from"@fjall/generator";import{DEFAULT_REGION as h}from"../../aws/utils/regions.js";import{getErrorMessage as k,maskSensitiveOutput as y}from"@fjall/util";import{CdkEventMonitor as R,startStackMonitoring as w}from"./CdkEventMonitoring.js";import{analyseDeployOutput as E,analyseDestroyResult as K,createEnhancedOutputCallback as N}from"./CdkOutputAnalyser.js";import{CdkCommandRunner as O}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as T}from"./CdkArgumentBuilder.js";import{CdkProcessManager as L}from"./CdkProcessManager.js";import{cancelInFlightStack as P}from"./cancelInFlightStack.js";import{isAborted as v}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as W}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as $,resolveStackName as M,getFallbackStackName as _,buildDeploymentCdkContext as b}from"./cdkServiceHelpers.js";class te{commandRunner;eventMonitor;abortSignal;constructor(e){const r=e?.processManager??new L(new T,e?.abortSignal);this.commandRunner=new O(r),this.eventMonitor=new R({eventLogWriterFactory:e?.eventLogWriterFactory,...e?.onFailureAnalysis!==void 0?{onFailureAnalysis:e.onFailureAnalysis}:{}}),this.abortSignal=e?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(e,r,t){return this.commandRunner.checkDifferences(e,r,t)}async deploy(e,r,t){return this.commandRunner.deploy(e,r,t)}async destroy(e,r,t){return this.commandRunner.destroy(e,r,t)}async runImport(e,r,t){return this.commandRunner.runImport(e,r,t)}async synth(e,r){return this.commandRunner.synth(e,r)}async bootstrap(e,r,t){return this.commandRunner.bootstrap(e,r,t)}async runCdkSynth(e,r,t){const n=e.callerIdentity?.Account;try{const o=await this.synth(e.path,{outputCallback:r,context:b(e,n,e.region||h),...e.assemblyDir!==void 0?{outputDir:e.assemblyDir}:{},...t!==void 0?{credentials:t}:{}});return o.success?C({message:"CloudFormation template synthesised",details:o.data.output?{synthesisTime:o.data.output}:void 0}):a(o.error||"Failed to synthesise CloudFormation template")}catch(o){return a(`CDK synth failed: ${y(k(o))}`)}}async runCdkBootstrap(e,r,t,n){const o=e.callerIdentity?.Account,f=e.region||h;try{if(!o)return a("No AWS account ID available");const c=D(e.path,"node_modules");if(!F(c))return a(`Dependencies not installed. Please run 'npm install' in ${e.path} before deploying.`);const l=await this.bootstrap(o,f,{outputCallback:r,credentials:t,...n!==void 0&&n!==""?{permissionsBoundary:n}:{}});return l.success?C({message:"AWS environment bootstrapped"}):a(l.error||"Failed to bootstrap AWS environment")}catch(c){return a(`CDK bootstrap failed: ${y(k(c))}`)}}async runCdkDiff(e,r){const t=e.callerIdentity?.Account;try{const n=await this.checkDifferences(e.path,void 0,{verbose:e.options?.verbose,outputCallback:r,context:b(e,t,e.region||h)});return n.success?C({message:"Diff check complete",details:{hasDifferences:n.data.hasDifferences,details:n.data.details}}):a(`CDK diff failed: ${n.error.message}`)}catch(n){return a(`CDK diff failed: ${y(k(n))}`)}}async runCdkDeploy(e,r,t,n,o,f,c){const l=e.callerIdentity?.Account,p=e.region||h;if(!l)return a("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!o)return a("AwsProvider is required for deployment monitoring.");const m=e.assemblyDir??D(e.path,"cdk.out"),d=W(m,n);let u=null,g;try{const i=M(r,e)??_(e);u=await this.eventMonitor.createEventMonitor("deploy",i,p,e,o),t&&(t(y(`Starting CloudFormation deployment of ${i}...
2
2
  `)),t(`Monitoring CloudFormation events (CDK process running in background)...
3
- `));const s={cdkOutput:"",actualStackName:i,stackDetected:!1,monitoringPromise:null},A=N(s,t,c,d);g=setTimeout(()=>{!s.stackDetected&&c&&!s.monitoringPromise&&(S.debug("CdkService","Fallback monitoring STARTING",{targetStackName:i,stackDetected:s.stackDetected,hasOnResourceProgress:!!n}),s.monitoringPromise=w(c,i,d))},$);const I=await this.deploy(e.path,i,{verbose:e.options?.verbose,outputCallback:A,...e.assemblyDir!==void 0?{appDir:e.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:c?.getEventLogger()??void 0,context:b(e,y,f),credentials:u,...l!==void 0&&Object.keys(l).length>0&&{parameters:l}});return v(this.abortSignal)?(await P(i,f,u,t),a("Deployment cancelled")):E(s.cdkOutput,I,s.actualStackName)}catch(i){const s=`CDK deploy failed: ${p(k(i))}`;return S.error("CdkService","CDK deployment exception",{error:s}),a(s)}finally{clearTimeout(g),c&&c.stopMonitoring()}}async runCdkDestroy(e,r,t,n,o,u,l){const y=e.callerIdentity?.Account,f=e.region||h;let m=null;try{const d=M(r,e);y&&d&&o&&(m=await this.eventMonitor.createEventMonitor("destroy",d,f,e,o),m.startMonitoring(d,g=>{n?.(g)},(g,i)=>{}));const c=await this.destroy(e.path,r,{verbose:e.options?.verbose,outputCallback:t,useCdkOut:u,cdkOutputLogger:m?.getEventLogger()??void 0,context:b(e,y,f),credentials:l});return v(this.abortSignal)?a("Destroy cancelled"):K(c)}catch(d){return a(`CDK destroy failed: ${p(k(d))}`)}finally{m&&m.stopMonitoring()}}}export{te as CdkService};
3
+ `));const s={cdkOutput:"",actualStackName:i,stackDetected:!1,monitoringPromise:null},A=N(s,t,u,d);g=setTimeout(()=>{!s.stackDetected&&u&&!s.monitoringPromise&&(S.debug("CdkService","Fallback monitoring STARTING",{targetStackName:i,stackDetected:s.stackDetected,hasOnResourceProgress:!!n}),s.monitoringPromise=w(u,i,d))},$);const I=await this.deploy(e.path,i,{verbose:e.options?.verbose,outputCallback:A,...e.assemblyDir!==void 0?{appDir:e.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:u?.getEventLogger()??void 0,context:b(e,l,p),credentials:f,...c!==void 0&&Object.keys(c).length>0&&{parameters:c}});return v(this.abortSignal)?(await P(i,p,f,t),a("Deployment cancelled")):E(s.cdkOutput,I,s.actualStackName)}catch(i){const s=`CDK deploy failed: ${y(k(i))}`;return S.error("CdkService","CDK deployment exception",{error:s}),a(s)}finally{clearTimeout(g),u&&u.stopMonitoring()}}async runCdkDestroy(e,r,t,n,o,f,c){const l=e.callerIdentity?.Account,p=e.region||h;let m=null;try{const d=M(r,e);l&&d&&o&&(m=await this.eventMonitor.createEventMonitor("destroy",d,p,e,o),m.startMonitoring(d,g=>{n?.(g)},(g,i)=>{}));const u=await this.destroy(e.path,r,{verbose:e.options?.verbose,outputCallback:t,useCdkOut:f,cdkOutputLogger:m?.getEventLogger()??void 0,context:b(e,l,p),credentials:c});return v(this.abortSignal)?a("Destroy cancelled"):K(u)}catch(d){return a(`CDK destroy failed: ${y(k(d))}`)}finally{m&&m.stopMonitoring()}}}export{te as CdkService};
@@ -41,6 +41,15 @@ export interface CdkOptions {
41
41
  */
42
42
  parameters?: Record<string, string>;
43
43
  timeout?: number;
44
+ /**
45
+ * Name of a pre-existing customer-managed IAM policy passed to
46
+ * `cdk bootstrap --custom-permissions-boundary <name>` — bounds the CDK
47
+ * `cfn-exec-role` (the second admin surface). CDK LOOKS THIS UP BY NAME; the
48
+ * policy MUST already exist in the target account (created by the
49
+ * oidc-connector stack — Phase E2), so the caller only sets it once the
50
+ * boundary is known to exist. Absent/empty ⇒ today's behaviour (no boundary).
51
+ */
52
+ permissionsBoundary?: string;
44
53
  passThroughCDK?: boolean;
45
54
  stacks?: string[];
46
55
  noLookups?: boolean;
@@ -10,6 +10,21 @@
10
10
  * backward-compatible: the engine deploys exactly as it does today.
11
11
  */
12
12
  import type { DeployPlan } from "../orchestration/application/plan/types.js";
13
+ /**
14
+ * Both detection signals behind a computed plan, distinguished so a parser can
15
+ * see WHY the gate stopped: hash drift (templates vs the last recorded deploy)
16
+ * is what engages the exit-2 outcome even when the resource-level diff against
17
+ * the live stacks is empty. Neither signal drives the exit code differently —
18
+ * this is observability, not control flow.
19
+ */
20
+ export interface ApprovalGatePlanDetail {
21
+ /** Stacks whose synthesised template hash differs from the recorded deploy state. */
22
+ hashDriftStacks: string[];
23
+ /** Count of resource-level changes diffed against the live stacks. */
24
+ resourceChangeCount: number;
25
+ /** Stacks with at least one resource-level change against the live stacks. */
26
+ resourceChangedStacks: string[];
27
+ }
13
28
  /** What the gate hands a surface resolver so it can decide. */
14
29
  export interface ApprovalRequest {
15
30
  /** Target app / deployment name. */
@@ -11,7 +11,7 @@ export type { ProgressEvent, ProgressEventType, ResourceEvent, AwsAuthResult, Ca
11
11
  export { TRAIL_MIGRATION_PHASES, TRAIL_MIGRATION_STATUSES } from "./events.js";
12
12
  export type { ApiClientInterface, EntitlementsData } from "./apiClient.js";
13
13
  export type { DeployParams, DeployOptions, DeploymentType, DeployResult, DestroyParams, DestroyOptions, DestroyResult } from "./params.js";
14
- export type { ApprovalGate, ApprovalRequest, GateResolution } from "./approval.js";
14
+ export type { ApprovalGate, ApprovalGatePlanDetail, ApprovalRequest, GateResolution } from "./approval.js";
15
15
  export type { OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession } from "./config/orgConfig.js";
16
16
  export type { Entitlements } from "./config/entitlements.js";
17
17
  export type { StackOutput, StackOutputsRecord, DeploymentContext, StepOutput, ApplicationDeploymentContext, CallerIdentity } from "./deployment/index.js";
@@ -1,11 +1,12 @@
1
1
  import type { Result } from "@fjall/generator";
2
+ import type { DeployMode, ServiceArtefact } from "@fjall/util";
2
3
  import type { AwsCredentials, DeployIdentity, ScopedBuildSecretSessionMinter } from "./credentials.js";
3
4
  import type { DeployCallbacks } from "./callbacks.js";
4
5
  import type { ApiClientInterface } from "./apiClient.js";
5
6
  import type { OrgConfig } from "./config/orgConfig.js";
6
7
  import type { DockerProvider } from "../orchestration/dockerInterface.js";
7
8
  import type { DomainDeployProvider } from "../orchestration/domainInterface.js";
8
- import type { ApprovalGate } from "./approval.js";
9
+ import type { ApprovalGate, ApprovalGatePlanDetail } from "./approval.js";
9
10
  import type { FailureAnalysis } from "@fjall/util/aws";
10
11
  import type { DockerBuildSecretRef } from "@fjall/util/manifest/schemas";
11
12
  export interface DeployParams {
@@ -77,6 +78,16 @@ export interface DeployParams {
77
78
  approvalGate?: ApprovalGate;
78
79
  }
79
80
  export interface DeployOptions {
81
+ /**
82
+ * Explicit deploy mode. When supplied it is authoritative — the engine
83
+ * branches on it alone. When omitted the engine derives it for legacy
84
+ * callers: `imageTag` present ⇒ `"rollback"`, else `deployOnly` ⇒
85
+ * `"code-only"`, else `"full"`. An explicit `"full"`/`"code-only"`
86
+ * combined with `imageTag` is rejected up-front (--image-tag requires
87
+ * rollback mode), making the redrive mode/imageTag mismatch (F9)
88
+ * unrepresentable.
89
+ */
90
+ mode?: DeployMode;
80
91
  skipConfirmation?: boolean;
81
92
  force?: boolean;
82
93
  cascade?: boolean;
@@ -151,6 +162,17 @@ export interface DeployResult {
151
162
  deploymentType: DeploymentType;
152
163
  /** Stack outputs (website URL, OIDC role ARN, etc.) */
153
164
  outputs?: Record<string, string>;
165
+ /**
166
+ * Typed per-service image identity for every service this deploy rolled or
167
+ * pushed (`ServiceArtefactSchema` at `@fjall/util`). Populated on ALL
168
+ * application deploy modes — code-only and rollback from the explicit
169
+ * RegisterTaskDefinition rollout, full deploys from the build step plus a
170
+ * best-effort post-deploy ECS resolution (a resolution failure degrades
171
+ * `taskDefinitionArn` to absent, never fails the deploy). Empty for
172
+ * organisation deploys, cancelled/rejected/awaiting outcomes, and deploys
173
+ * that pushed no images.
174
+ */
175
+ artefacts: ServiceArtefact[];
154
176
  /** Per-account outputs from cascade deploys (accountId → stack outputs). */
155
177
  cascadeOutputs?: Array<{
156
178
  accountId: string;
@@ -208,6 +230,14 @@ export interface DeployResult {
208
230
  * plan.
209
231
  */
210
232
  assemblyDigest?: string;
233
+ /**
234
+ * Approval-gate: both detection signals behind the gate outcome (template-hash
235
+ * drift vs the recorded deploy state; resource-level diff vs the live stacks).
236
+ * Present whenever the gate computed a plan — always with `awaitingApproval`,
237
+ * and with `rejected` only when a resolver declined a computed plan (a refused
238
+ * resume token never computed one).
239
+ */
240
+ planDetail?: ApprovalGatePlanDetail;
211
241
  }
212
242
  /**
213
243
  * Parameters for the destroy entry point.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/deploy-core",
3
- "version": "2.23.0",
3
+ "version": "2.24.0",
4
4
  "description": "Shared deployment engine for Fjall — used by CLI and webapp worker",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -72,6 +72,7 @@
72
72
  "@aws-sdk/client-ec2": "^3.1038.0",
73
73
  "@aws-sdk/client-ecr": "^3.1039.0",
74
74
  "@aws-sdk/client-iam": "^3.1038.0",
75
+ "@aws-sdk/client-identitystore": "^3.1079.0",
75
76
  "@aws-sdk/client-kms": "^3.1065.0",
76
77
  "@aws-sdk/client-organizations": "^3.1038.0",
77
78
  "@aws-sdk/client-ram": "^3.1038.0",
@@ -81,8 +82,8 @@
81
82
  "@aws-sdk/client-ssm": "^3.1038.0",
82
83
  "@aws-sdk/client-sso-admin": "^3.1038.0",
83
84
  "@aws-sdk/client-sts": "^3.1038.0",
84
- "@fjall/generator": "^2.23.0",
85
- "@fjall/util": "^2.23.0",
85
+ "@fjall/generator": "^2.24.0",
86
+ "@fjall/util": "^2.24.0",
86
87
  "@smithy/node-http-handler": "^4.6.1",
87
88
  "aws-cdk": "^2.1128.1",
88
89
  "tsx": "^4.21.0",
@@ -93,5 +94,5 @@
93
94
  "typescript": "^6.0.3",
94
95
  "vitest": "^4.1.5"
95
96
  },
96
- "gitHead": "dadd84e6118f03252877974eec755e812cb5b42f"
97
+ "gitHead": "616d7af846f0eebfa3409f5f47c98fbee836bad9"
97
98
  }