@fjall/deploy-core 2.14.0 → 2.16.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 (59) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/SimpleAwsProvider.js +1 -1
  3. package/dist/src/aws/cloudtrail/orgTrailDelivery.js +1 -1
  4. package/dist/src/aws/organisations/accounts.d.ts +1 -1
  5. package/dist/src/aws/organisations/accounts.js +1 -1
  6. package/dist/src/aws/organisations/backup.d.ts +3 -3
  7. package/dist/src/aws/organisations/backup.js +2 -2
  8. package/dist/src/aws/organisations/costAllocation.d.ts +1 -1
  9. package/dist/src/aws/organisations/costAllocation.js +1 -1
  10. package/dist/src/aws/organisations/delegatedAdmin.d.ts +1 -1
  11. package/dist/src/aws/organisations/delegatedAdmin.js +3 -3
  12. package/dist/src/aws/organisations/identityCentre.d.ts +1 -1
  13. package/dist/src/aws/organisations/identityCentre.js +1 -1
  14. package/dist/src/aws/organisations/ipam.d.ts +1 -1
  15. package/dist/src/aws/organisations/ipam.js +1 -1
  16. package/dist/src/aws/organisations/organisation.d.ts +2 -2
  17. package/dist/src/aws/organisations/organisation.js +1 -1
  18. package/dist/src/aws/organisations/organisationalUnits.d.ts +2 -2
  19. package/dist/src/aws/organisations/organisationalUnits.js +1 -1
  20. package/dist/src/aws/organisations/policies.js +1 -1
  21. package/dist/src/aws/organisations/ram.d.ts +1 -1
  22. package/dist/src/aws/organisations/ram.js +1 -1
  23. package/dist/src/aws/organisations/rootAccess.js +3 -3
  24. package/dist/src/aws/organisations/serviceAccess.d.ts +1 -1
  25. package/dist/src/aws/organisations/serviceAccess.js +1 -1
  26. package/dist/src/aws/organisations/trustedAccess.d.ts +1 -1
  27. package/dist/src/aws/organisations/trustedAccess.js +1 -1
  28. package/dist/src/aws/organisations/types.d.ts +6 -1
  29. package/dist/src/aws/organisations/types.js +1 -1
  30. package/dist/src/index.d.ts +2 -0
  31. package/dist/src/index.js +1 -1
  32. package/dist/src/orchestration/applicationDeploy.js +1 -1
  33. package/dist/src/orchestration/cascadeDestroyHelpers.js +1 -1
  34. package/dist/src/orchestration/cascadeHelpers.js +1 -1
  35. package/dist/src/orchestration/dockerBuildHelper.js +1 -1
  36. package/dist/src/orchestration/index.d.ts +8 -0
  37. package/dist/src/orchestration/index.js +1 -1
  38. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +4 -4
  39. package/dist/src/orchestration/organisationDeploy/orgContext.d.ts +1 -1
  40. package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -1
  41. package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -1
  42. package/dist/src/orchestration/organisationSetup.js +1 -1
  43. package/dist/src/orchestration/secretArnResolver.js +1 -1
  44. package/dist/src/orchestration/stackCleanup/bucketOps.js +1 -1
  45. package/dist/src/orchestration/unlock/bucketPolicyTriage.d.ts +82 -0
  46. package/dist/src/orchestration/unlock/bucketPolicyTriage.js +1 -0
  47. package/dist/src/orchestration/unlock/restoreAndReconcileQuarantinedBucket.d.ts +67 -0
  48. package/dist/src/orchestration/unlock/restoreAndReconcileQuarantinedBucket.js +1 -0
  49. package/dist/src/orchestration/unlock/restoreBucketPolicy.d.ts +43 -0
  50. package/dist/src/orchestration/unlock/restoreBucketPolicy.js +1 -0
  51. package/dist/src/orchestration/unlock/toResourcePolicyStatements.d.ts +20 -0
  52. package/dist/src/orchestration/unlock/toResourcePolicyStatements.js +1 -0
  53. package/dist/src/orchestration/unlock/unlockBucket.d.ts +12 -0
  54. package/dist/src/orchestration/unlock/unlockBucket.js +1 -1
  55. package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +15 -4
  56. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -1
  57. package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -2
  58. package/dist/src/services/infrastructure/CdkProcessManager.js +3 -3
  59. package/package.json +4 -4
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 148 files minified at 2026-06-12T01:12:37.959Z
1
+ 152 files minified at 2026-06-14T08:04:17.640Z
@@ -1 +1 @@
1
- import{STSClient as i,AssumeRoleCommand as r}from"@aws-sdk/client-sts";import{NodeHttpHandler as d}from"@smithy/node-http-handler";const o=3e4;class u{credentials;region;accountId;clientCache=new Map;constructor(e){this.credentials={accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey,sessionToken:e.sessionToken},this.region=e.region,this.accountId=e.accountId}getClient(e){const t=e.name,n=this.clientCache.get(t);if(n!==void 0)return n;const c={region:this.region,credentials:this.credentials,requestHandler:new d({requestTimeout:o})},s=new e(c);return this.clientCache.set(t,s),s}getRegion(){return this.region}getAccountId(){return this.accountId}getCredentials(){return this.credentials}exportToEnv(){process.env.AWS_ACCESS_KEY_ID=this.credentials.accessKeyId,process.env.AWS_SECRET_ACCESS_KEY=this.credentials.secretAccessKey,process.env.AWS_REGION=this.region,this.credentials.sessionToken?process.env.AWS_SESSION_TOKEN=this.credentials.sessionToken:delete process.env.AWS_SESSION_TOKEN}async assumeRole(e,t){const s=(await this.getClient(i).send(new r({RoleArn:e,RoleSessionName:t}),{abortSignal:AbortSignal.timeout(o)})).Credentials;if(!s?.AccessKeyId||!s?.SecretAccessKey)throw new Error(`AssumeRole for ${e} returned incomplete credentials`);return{accessKeyId:s.AccessKeyId,secretAccessKey:s.SecretAccessKey,sessionToken:s.SessionToken}}}export{u as SimpleAwsProvider};
1
+ import{STSClient as r,AssumeRoleCommand as i}from"@aws-sdk/client-sts";import{NodeHttpHandler as d}from"@smithy/node-http-handler";import{composeSdkAbortSignal as a}from"./organisations/types.js";const o=3e4;class A{credentials;region;accountId;clientCache=new Map;constructor(e){this.credentials={accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey,sessionToken:e.sessionToken},this.region=e.region,this.accountId=e.accountId}getClient(e){const n=e.name,t=this.clientCache.get(n);if(t!==void 0)return t;const c={region:this.region,credentials:this.credentials,requestHandler:new d({requestTimeout:o})},s=new e(c);return this.clientCache.set(n,s),s}getRegion(){return this.region}getAccountId(){return this.accountId}getCredentials(){return this.credentials}exportToEnv(){process.env.AWS_ACCESS_KEY_ID=this.credentials.accessKeyId,process.env.AWS_SECRET_ACCESS_KEY=this.credentials.secretAccessKey,process.env.AWS_REGION=this.region,this.credentials.sessionToken?process.env.AWS_SESSION_TOKEN=this.credentials.sessionToken:delete process.env.AWS_SESSION_TOKEN}async assumeRole(e,n){const s=(await this.getClient(r).send(new i({RoleArn:e,RoleSessionName:n}),{abortSignal:a(void 0,o)})).Credentials;if(!s?.AccessKeyId||!s?.SecretAccessKey)throw new Error(`AssumeRole for ${e} returned incomplete credentials`);return{accessKeyId:s.AccessKeyId,secretAccessKey:s.SecretAccessKey,sessionToken:s.SessionToken}}}export{A as SimpleAwsProvider};
@@ -1 +1 @@
1
- import{ListObjectsV2Command as M}from"@aws-sdk/client-s3";import{GetTrailStatusCommand as D}from"@aws-sdk/client-cloudtrail";import{success as h,failure as S}from"@fjall/generator";import{getErrorMessage as E,maskSensitiveOutput as P}from"@fjall/util";import{SDK_TIMEOUT_MS as k,extractErrorName as w}from"../organisations/types.js";const f=1440*60*1e3,x=f,v=50,A=2;function b(n){const e=AbortSignal.timeout(k);return n!==void 0?AbortSignal.any([n,e]):e}function N(n,e){const r=[],t=Math.floor((e-n)/f)*f;for(let c=t;c<=e;c+=f){const i=new Date(c),s=String(i.getUTCMonth()+1).padStart(2,"0"),o=String(i.getUTCDate()).padStart(2,"0");r.push(`${i.getUTCFullYear()}/${s}/${o}`)}return r.reverse()}async function I(n,e){let r=!1,t,c=!1,i=!1;const s=a=>{for(const u of a.Contents??[])r=!0,u.LastModified!==void 0&&((t===void 0||u.LastModified>t)&&(t=u.LastModified),u.LastModified.getTime()>=e.recentSinceMs&&(c=!0))},o=a=>({delivered:r,recentDelivery:a,...t!==void 0?{latestObjectAt:t}:{}}),d=await n.send(new M({Bucket:e.bucketName,Prefix:e.accountPrefix,Delimiter:"/"}),{abortSignal:b(e.abortSignal)});s(d);const y=(d.CommonPrefixes??[]).map(a=>a.Prefix).filter(a=>a!==void 0);if(d.IsTruncated===!0||y.length>v)return o(void 0);for(const a of y)for(const u of e.datePaths){let l,g=!1;for(let T=0;T<A&&!g;T++){const m=await n.send(new M({Bucket:e.bucketName,Prefix:`${a}${u}/`,...l!==void 0?{ContinuationToken:l}:{}}),{abortSignal:b(e.abortSignal)});if(s(m),c)return o(!0);m.IsTruncated===!0?l=m.NextContinuationToken:g=!0}g||(i=!0)}return o(i?void 0:!1)}async function O(n,e){const r=e.recencyWindowMs??x,t=Date.now(),c=N(r,t),i=[];for(const s of e.accountIds)try{const o=await I(n,{bucketName:e.bucketName,accountPrefix:`AWSLogs/${e.orgId}/${s}/CloudTrail/`,datePaths:c,recentSinceMs:t-r,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});i.push({accountId:s,...o})}catch(o){return w(o)==="NoSuchBucket"?S(new Error(`Organisation trail bucket ${e.bucketName} does not exist \u2014 the org trail has not been deployed yet.`)):S(new Error(`Failed to verify org-trail delivery for account ${s}: ${P(E(o))}`))}return h({bucketName:e.bucketName,perAccount:i})}async function F(n,e,r){try{const t=await n.send(new D({Name:e}),{abortSignal:b(r)});return h({isLogging:t.IsLogging===!0,...t.LatestDeliveryTime!==void 0?{latestDeliveryTime:t.LatestDeliveryTime}:{}})}catch(t){return S(new Error(`Failed to read trail status for ${e}: ${P(E(t))}`))}}export{F as getTrailLoggingStatus,O as verifyOrgTrailDelivery};
1
+ import{ListObjectsV2Command as w}from"@aws-sdk/client-s3";import{GetTrailStatusCommand as P}from"@aws-sdk/client-cloudtrail";import{success as T,failure as S}from"@fjall/generator";import{getErrorMessage as E,maskSensitiveOutput as M}from"@fjall/util";import{composeSdkAbortSignal as b,extractErrorName as k,isAborted as x}from"../organisations/types.js";const d=1440*60*1e3,D=d,v=50,A=2;function N(a,e){const n=[],t=Math.floor((e-a)/d)*d;for(let c=t;c<=e;c+=d){const o=new Date(c),s=String(o.getUTCMonth()+1).padStart(2,"0"),r=String(o.getUTCDate()).padStart(2,"0");n.push(`${o.getUTCFullYear()}/${s}/${r}`)}return n.reverse()}async function L(a,e){let n=!1,t,c=!1,o=!1;const s=i=>{for(const u of i.Contents??[])n=!0,u.LastModified!==void 0&&((t===void 0||u.LastModified>t)&&(t=u.LastModified),u.LastModified.getTime()>=e.recentSinceMs&&(c=!0))},r=i=>({delivered:n,recentDelivery:i,...t!==void 0?{latestObjectAt:t}:{}}),f=await a.send(new w({Bucket:e.bucketName,Prefix:e.accountPrefix,Delimiter:"/"}),{abortSignal:b(e.abortSignal)});s(f);const y=(f.CommonPrefixes??[]).map(i=>i.Prefix).filter(i=>i!==void 0);if(f.IsTruncated===!0||y.length>v)return r(void 0);for(const i of y)for(const u of e.datePaths){let l,g=!1;for(let h=0;h<A&&!g;h++){if(x(e.abortSignal))throw new Error("Aborted: org-trail delivery scan cancelled by shutdown signal");const m=await a.send(new w({Bucket:e.bucketName,Prefix:`${i}${u}/`,...l!==void 0?{ContinuationToken:l}:{}}),{abortSignal:b(e.abortSignal)});if(s(m),c)return r(!0);m.IsTruncated===!0?l=m.NextContinuationToken:g=!0}g||(o=!0)}return r(o?void 0:!1)}async function F(a,e){const n=e.recencyWindowMs??D,t=Date.now(),c=N(n,t),o=[];for(const s of e.accountIds)try{const r=await L(a,{bucketName:e.bucketName,accountPrefix:`AWSLogs/${e.orgId}/${s}/CloudTrail/`,datePaths:c,recentSinceMs:t-n,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});o.push({accountId:s,...r})}catch(r){return k(r)==="NoSuchBucket"?S(new Error(`Organisation trail bucket ${e.bucketName} does not exist \u2014 the org trail has not been deployed yet.`)):S(new Error(`Failed to verify org-trail delivery for account ${s}: ${M(E(r))}`))}return T({bucketName:e.bucketName,perAccount:o})}async function O(a,e,n){try{const t=await a.send(new P({Name:e}),{abortSignal:b(n)});return T({isLogging:t.IsLogging===!0,...t.LatestDeliveryTime!==void 0?{latestDeliveryTime:t.LatestDeliveryTime}:{}})}catch(t){return S(new Error(`Failed to read trail status for ${e}: ${M(E(t))}`))}}export{O as getTrailLoggingStatus,F as verifyOrgTrailDelivery};
@@ -7,7 +7,7 @@ export interface CreateAccountResult {
7
7
  /**
8
8
  * List all accounts in the organisation, handling pagination.
9
9
  */
10
- export declare function listAccounts(client: OrganizationsClient): Promise<Result<Account[]>>;
10
+ export declare function listAccounts(client: OrganizationsClient, abortSignal?: AbortSignal): Promise<Result<Account[]>>;
11
11
  /**
12
12
  * Find an account by ID in the organisation.
13
13
  */
@@ -1 +1 @@
1
- import{ListAccountsCommand as f,CreateAccountCommand as b,CreateAccountState as w,DescribeCreateAccountStatusCommand as $}from"@aws-sdk/client-organizations";import{success as A,failure as r}from"@fjall/generator";import{getErrorMessage as E}from"@fjall/util";import{extractErrorName as p,composeSdkAbortSignal as S,isAborted as m,SDK_TIMEOUT_MS as g,AWS_ERROR_NAMES as C}from"./types.js";import{sleepAbortable as h}from"../../util/sleepAbortable.js";async function x(o){try{const t=await o.send(new f({MaxResults:20}),{abortSignal:AbortSignal.timeout(g)});let e=t.Accounts??[],n=t.NextToken;for(;n;){const c=await o.send(new f({MaxResults:20,NextToken:n}),{abortSignal:AbortSignal.timeout(g)});e=e.concat(c.Accounts??[]),n=c.NextToken}return A(e)}catch(t){return p(t)===C.ORGS_NOT_IN_USE?r(new Error("AWS Organisations is not enabled for this account")):r(new Error(`Failed to list accounts: ${E(t)}`))}}async function _(o,t){const e=await x(o);return e.success?A(e.data.find(n=>n.Id===t)):e}async function O(o,t,e,n=180,c=5e3,a){if(m(a))return r(new Error(`Aborted: account creation for "${t}" cancelled by shutdown signal before starting`));try{let u;try{u=await o.send(new b({AccountName:t,Email:e}),{abortSignal:S(a)})}catch(i){const l=p(i);if(l===C.ACCESS_DENIED)return r(new Error(`Access denied when creating account "${t}". Ensure your credentials have organizations:CreateAccount permission.`));if(l==="DuplicateAccountException")return r(new Error(`An account with the email "${e}" already exists in the organisation.`));if(l==="FinalizingOrganizationException")return r(new Error("The organisation is still being initialised. Please wait and try again."));throw i}const d=u.CreateAccountStatus?.Id;if(!d)return r(new Error(`CreateAccount request for "${t}" did not return a status ID`));for(let i=0;i<n;i++){if(m(a))return r(new Error(`Aborted: account creation polling for "${t}" cancelled by shutdown signal \u2014 AWS request ${d} may still complete server-side`));const s=(await o.send(new $({CreateAccountRequestId:d}),{abortSignal:S(a)})).CreateAccountStatus;if(!s)return r(new Error(`No status returned for CreateAccount request ${d}`));if(s.State===w.SUCCEEDED)return s.AccountId?A({accountId:s.AccountId,accountName:t}):r(new Error(`Account creation succeeded but no account ID returned for "${t}"`));if(s.State===w.FAILED)return r(new Error(`Account creation failed for "${t}": ${s.FailureReason}`));await h(c,a)}return r(new Error(`Account creation for "${t}" timed out after ${n*c/1e3} seconds`))}catch(u){return r(new Error(`Failed to create account "${t}": ${E(u)}`))}}export{O as createAccount,_ as findAccount,x as listAccounts};
1
+ import{ListAccountsCommand as E,CreateAccountCommand as h,CreateAccountState as p,DescribeCreateAccountStatusCommand as S}from"@aws-sdk/client-organizations";import{success as A,failure as e}from"@fjall/generator";import{getErrorMessage as m}from"@fjall/util";import{extractErrorName as g,composeSdkAbortSignal as f,isAborted as w,AWS_ERROR_NAMES as C}from"./types.js";import{sleepAbortable as $}from"../../util/sleepAbortable.js";async function b(s,r){try{const t=await s.send(new E({MaxResults:20}),{abortSignal:f(r)});let n=t.Accounts??[],c=t.NextToken;for(;c;){if(w(r))return e(new Error("Aborted: account listing cancelled by shutdown signal"));const o=await s.send(new E({MaxResults:20,NextToken:c}),{abortSignal:f(r)});n=n.concat(o.Accounts??[]),c=o.NextToken}return A(n)}catch(t){return g(t)===C.ORGS_NOT_IN_USE?e(new Error("AWS Organisations is not enabled for this account")):e(new Error(`Failed to list accounts: ${m(t)}`))}}async function T(s,r){const t=await b(s);return t.success?A(t.data.find(n=>n.Id===r)):t}async function _(s,r,t,n=180,c=5e3,o){if(w(o))return e(new Error(`Aborted: account creation for "${r}" cancelled by shutdown signal before starting`));try{let u;try{u=await s.send(new h({AccountName:r,Email:t}),{abortSignal:f(o)})}catch(i){const l=g(i);if(l===C.ACCESS_DENIED)return e(new Error(`Access denied when creating account "${r}". Ensure your credentials have organizations:CreateAccount permission.`));if(l==="DuplicateAccountException")return e(new Error(`An account with the email "${t}" already exists in the organisation.`));if(l==="FinalizingOrganizationException")return e(new Error("The organisation is still being initialised. Please wait and try again."));throw i}const d=u.CreateAccountStatus?.Id;if(!d)return e(new Error(`CreateAccount request for "${r}" did not return a status ID`));for(let i=0;i<n;i++){if(w(o))return e(new Error(`Aborted: account creation polling for "${r}" cancelled by shutdown signal \u2014 AWS request ${d} may still complete server-side`));const a=(await s.send(new S({CreateAccountRequestId:d}),{abortSignal:f(o)})).CreateAccountStatus;if(!a)return e(new Error(`No status returned for CreateAccount request ${d}`));if(a.State===p.SUCCEEDED)return a.AccountId?A({accountId:a.AccountId,accountName:r}):e(new Error(`Account creation succeeded but no account ID returned for "${r}"`));if(a.State===p.FAILED)return e(new Error(`Account creation failed for "${r}": ${a.FailureReason}`));await $(c,o)}return e(new Error(`Account creation for "${r}" timed out after ${n*c/1e3} seconds`))}catch(u){return e(new Error(`Failed to create account "${r}": ${m(u)}`))}}export{_ as createAccount,T as findAccount,b as listAccounts};
@@ -11,7 +11,7 @@ export interface BackupGlobalSettings {
11
11
  * Update AWS Backup global settings for the organisation.
12
12
  * Idempotent — safe to call repeatedly with the same settings.
13
13
  */
14
- export declare function updateBackupGlobalSettings(client: BackupClient, settings?: BackupGlobalSettings): Promise<Result<void>>;
14
+ export declare function updateBackupGlobalSettings(client: BackupClient, settings?: BackupGlobalSettings, abortSignal?: AbortSignal): Promise<Result<void>>;
15
15
  /**
16
16
  * Whether an account's synthesised stack will create the DR backup vault — and
17
17
  * so whether the adopt-vs-create probe needs to run for it. Mirror of the
@@ -34,7 +34,7 @@ export declare function accountHasDisasterRecovery(environment: string | null |
34
34
  * propagates as failure — a mis-read "exists" would flip a vault-less region
35
35
  * into a broken by-reference deploy.
36
36
  */
37
- export declare function describeBackupVaultExists(client: BackupClient): Promise<Result<boolean>>;
37
+ export declare function describeBackupVaultExists(client: BackupClient, abortSignal?: AbortSignal): Promise<Result<boolean>>;
38
38
  /**
39
39
  * A backup vault that survives stack teardown (vault + KMS key are
40
40
  * RemovalPolicy.RETAIN). Populated from a single DescribeBackupVault call —
@@ -56,7 +56,7 @@ export interface SurvivingBackupVault {
56
56
  * success(null) when no vault exists (ResourceNotFoundException); every other
57
57
  * error propagates as failure for the caller to treat as best-effort.
58
58
  */
59
- export declare function describeSurvivingBackupVault(client: BackupClient, region: string): Promise<Result<SurvivingBackupVault | null>>;
59
+ export declare function describeSurvivingBackupVault(client: BackupClient, region: string, abortSignal?: AbortSignal): Promise<Result<SurvivingBackupVault | null>>;
60
60
  /**
61
61
  * Honest, non-suppressible warning for a backup vault a destroy cannot remove.
62
62
  * The vault and its KMS key are RemovalPolicy.RETAIN — CloudFormation marks them
@@ -1,2 +1,2 @@
1
- import{DescribeBackupVaultCommand as s,UpdateGlobalSettingsCommand as p}from"@aws-sdk/client-backup";import{success as a,failure as i}from"@fjall/generator";import{BACKUP_VAULT_NAME as o,getErrorMessage as c,maskSensitiveOutput as l}from"@fjall/util";import{SDK_TIMEOUT_MS as u}from"./types.js";const m={enableCrossAccountBackup:!0,enableDelegatedAdministrator:!0,enableMpa:!1};async function y(e,t){try{const n={...m,...t},r={isCrossAccountBackupEnabled:n.enableCrossAccountBackup.toString(),isDelegatedAdministratorEnabled:n.enableDelegatedAdministrator.toString(),isMpaEnabled:n.enableMpa.toString()};return await e.send(new p({GlobalSettings:r}),{abortSignal:AbortSignal.timeout(u)}),a(void 0)}catch(n){return i(new Error(`Failed to update backup global settings: ${c(n)}`))}}function S(e,t){return t===void 0||t===""?!1:e==="production"||e==="compliance"}async function A(e){try{return await e.send(new s({BackupVaultName:o}),{abortSignal:AbortSignal.timeout(u)}),a(!0)}catch(t){return t instanceof Error&&t.name==="ResourceNotFoundException"?a(!1):i(new Error(`Failed to describe backup vault "${o}": ${l(c(t))}`))}}async function E(e,t){try{const n=await e.send(new s({BackupVaultName:o}),{abortSignal:AbortSignal.timeout(u)}),r=n.LockDate,d=r!==void 0&&r.getTime()<=Date.now();return a({vaultName:n.BackupVaultName??o,region:t,locked:n.Locked??!1,...r!==void 0&&{lockDate:r},lockPermanent:d,recoveryPointCount:n.NumberOfRecoveryPoints??0,...n.EncryptionKeyArn!==void 0&&{encryptionKeyArn:n.EncryptionKeyArn}})}catch(n){return n instanceof Error&&n.name==="ResourceNotFoundException"?a(null):i(new Error(`Failed to describe surviving backup vault "${o}": ${l(c(n))}`))}}function v(e){const t=e.locked?e.lockPermanent?"compliance-locked, PERMANENT \u2014 cannot be removed by anyone, including AWS or the root user":e.lockDate!==void 0?`compliance-locked, immutable from ${e.lockDate.toISOString().slice(0,10)}`:"governance-locked \u2014 removable by privileged IAM":"unlocked";return[`Backup vault "${e.vaultName}" in ${e.region} SURVIVES this destroy.`,` \u2022 Lock: ${t}`,` \u2022 Recovery points retained: ${e.recoveryPointCount}`,...e.encryptionKeyArn!==void 0?[` \u2022 Retained KMS key (also orphaned): ${e.encryptionKeyArn}`]:[]," The vault and its KMS key are RemovalPolicy.RETAIN \u2014 destroy cannot delete them."].join(`
2
- `)}export{o as BACKUP_VAULT_NAME,S as accountHasDisasterRecovery,A as describeBackupVaultExists,E as describeSurvivingBackupVault,v as formatSurvivingVaultWarning,y as updateBackupGlobalSettings};
1
+ import{DescribeBackupVaultCommand as l,UpdateGlobalSettingsCommand as m}from"@aws-sdk/client-backup";import{success as i,failure as c}from"@fjall/generator";import{BACKUP_VAULT_NAME as a,getErrorMessage as s,maskSensitiveOutput as d}from"@fjall/util";import{composeSdkAbortSignal as u}from"./types.js";const k={enableCrossAccountBackup:!0,enableDelegatedAdministrator:!0,enableMpa:!1};async function S(e,t,r){try{const n={...k,...t},o={isCrossAccountBackupEnabled:n.enableCrossAccountBackup.toString(),isDelegatedAdministratorEnabled:n.enableDelegatedAdministrator.toString(),isMpaEnabled:n.enableMpa.toString()};return await e.send(new m({GlobalSettings:o}),{abortSignal:u(r)}),i(void 0)}catch(n){return c(new Error(`Failed to update backup global settings: ${s(n)}`))}}function A(e,t){return t===void 0||t===""?!1:e==="production"||e==="compliance"}async function E(e,t){try{return await e.send(new l({BackupVaultName:a}),{abortSignal:u(t)}),i(!0)}catch(r){return r instanceof Error&&r.name==="ResourceNotFoundException"?i(!1):c(new Error(`Failed to describe backup vault "${a}": ${d(s(r))}`))}}async function v(e,t,r){try{const n=await e.send(new l({BackupVaultName:a}),{abortSignal:u(r)}),o=n.LockDate,p=o!==void 0&&o.getTime()<=Date.now();return i({vaultName:n.BackupVaultName??a,region:t,locked:n.Locked??!1,...o!==void 0&&{lockDate:o},lockPermanent:p,recoveryPointCount:n.NumberOfRecoveryPoints??0,...n.EncryptionKeyArn!==void 0&&{encryptionKeyArn:n.EncryptionKeyArn}})}catch(n){return n instanceof Error&&n.name==="ResourceNotFoundException"?i(null):c(new Error(`Failed to describe surviving backup vault "${a}": ${d(s(n))}`))}}function B(e){const t=e.locked?e.lockPermanent?"compliance-locked, PERMANENT \u2014 cannot be removed by anyone, including AWS or the root user":e.lockDate!==void 0?`compliance-locked, immutable from ${e.lockDate.toISOString().slice(0,10)}`:"governance-locked \u2014 removable by privileged IAM":"unlocked";return[`Backup vault "${e.vaultName}" in ${e.region} SURVIVES this destroy.`,` \u2022 Lock: ${t}`,` \u2022 Recovery points retained: ${e.recoveryPointCount}`,...e.encryptionKeyArn!==void 0?[` \u2022 Retained KMS key (also orphaned): ${e.encryptionKeyArn}`]:[]," The vault and its KMS key are RemovalPolicy.RETAIN \u2014 destroy cannot delete them."].join(`
2
+ `)}export{a as BACKUP_VAULT_NAME,A as accountHasDisasterRecovery,E as describeBackupVaultExists,v as describeSurvivingBackupVault,B as formatSurvivingVaultWarning,S as updateBackupGlobalSettings};
@@ -9,4 +9,4 @@ export interface CostAllocationTag {
9
9
  *
10
10
  * Returns success with no action if the tags array is empty.
11
11
  */
12
- export declare function activateCostAllocationTags(client: CostExplorerClient, tags: CostAllocationTag[]): Promise<Result<void>>;
12
+ export declare function activateCostAllocationTags(client: CostExplorerClient, tags: CostAllocationTag[], abortSignal?: AbortSignal): Promise<Result<void>>;
@@ -1 +1 @@
1
- import{UpdateCostAllocationTagsStatusCommand as n}from"@aws-sdk/client-cost-explorer";import{success as a,failure as i}from"@fjall/generator";import{getErrorMessage as s}from"@fjall/util";import{SDK_TIMEOUT_MS as c}from"./types.js";async function f(r,o){if(o.length===0)return a(void 0);try{const t=o.map(e=>({TagKey:e.TagKey,Status:"Active"}));return await r.send(new n({CostAllocationTagsStatus:t}),{abortSignal:AbortSignal.timeout(c)}),a(void 0)}catch(t){return i(new Error(`Failed to activate cost allocation tags: ${s(t)}`))}}export{f as activateCostAllocationTags};
1
+ import{UpdateCostAllocationTagsStatusCommand as s}from"@aws-sdk/client-cost-explorer";import{success as a,failure as i}from"@fjall/generator";import{getErrorMessage as c}from"@fjall/util";import{composeSdkAbortSignal as l}from"./types.js";async function f(r,o,e){if(o.length===0)return a(void 0);try{const t=o.map(n=>({TagKey:n.TagKey,Status:"Active"}));return await r.send(new s({CostAllocationTagsStatus:t}),{abortSignal:l(e)}),a(void 0)}catch(t){return i(new Error(`Failed to activate cost allocation tags: ${c(t)}`))}}export{f as activateCostAllocationTags};
@@ -5,5 +5,5 @@ declare const SECURITY_SERVICE_PRINCIPALS: readonly ["guardduty.amazonaws.com",
5
5
  * Register delegated administrators for security services.
6
6
  * Idempotent — already-registered delegates are silently skipped.
7
7
  */
8
- export declare function registerSecurityDelegates(client: OrganizationsClient, delegateAccountId: string, services?: readonly string[]): Promise<Result<void>>;
8
+ export declare function registerSecurityDelegates(client: OrganizationsClient, delegateAccountId: string, services?: readonly string[], abortSignal?: AbortSignal): Promise<Result<void>>;
9
9
  export { SECURITY_SERVICE_PRINCIPALS };
@@ -1,3 +1,3 @@
1
- import{RegisterDelegatedAdministratorCommand as u}from"@aws-sdk/client-organizations";import{success as E,failure as a}from"@fjall/generator";import{extractErrorName as d,SDK_TIMEOUT_MS as f,AWS_ERROR_NAMES as i}from"./types.js";import{getErrorMessage as S}from"@fjall/util";const s=["guardduty.amazonaws.com","securityhub.amazonaws.com","config.amazonaws.com","inspector2.amazonaws.com","access-analyzer.amazonaws.com"];async function w(c,o,m=s){const r=[];for(const e of m)try{await c.send(new u({AccountId:o,ServicePrincipal:e}),{abortSignal:AbortSignal.timeout(f)})}catch(t){const n=d(t);if(n===i.ACCOUNT_ALREADY_REGISTERED)continue;if(n===i.ACCESS_DENIED){const g=r.length>0?`${r.join("; ")}; `:"";return a(new Error(`${g}Access denied when registering delegated admin for ${e}. Ensure your credentials have organizations:RegisterDelegatedAdministrator permission.`))}r.push(`${e}: ${S(t)}`)}return r.length>0?a(new Error(`Failed to register security delegates for account ${o}:
2
- ${r.join(`
3
- `)}`)):E(void 0)}export{s as SECURITY_SERVICE_PRINCIPALS,w as registerSecurityDelegates};
1
+ import{RegisterDelegatedAdministratorCommand as u}from"@aws-sdk/client-organizations";import{success as f,failure as n}from"@fjall/generator";import{extractErrorName as E,composeSdkAbortSignal as l,isAborted as p,AWS_ERROR_NAMES as s}from"./types.js";import{getErrorMessage as A}from"@fjall/util";const c=["guardduty.amazonaws.com","securityhub.amazonaws.com","config.amazonaws.com","inspector2.amazonaws.com","access-analyzer.amazonaws.com"];async function h(m,t,d=c,i){const e=[];for(const o of d){if(p(i)){const r=e.length>0?`${e.join("; ")}; `:"";return n(new Error(`${r}Aborted: security-delegate registration cancelled by shutdown signal`))}try{await m.send(new u({AccountId:t,ServicePrincipal:o}),{abortSignal:l(i)})}catch(r){const a=E(r);if(a===s.ACCOUNT_ALREADY_REGISTERED)continue;if(a===s.ACCESS_DENIED){const g=e.length>0?`${e.join("; ")}; `:"";return n(new Error(`${g}Access denied when registering delegated admin for ${o}. Ensure your credentials have organizations:RegisterDelegatedAdministrator permission.`))}e.push(`${o}: ${A(r)}`)}}return e.length>0?n(new Error(`Failed to register security delegates for account ${t}:
2
+ ${e.join(`
3
+ `)}`)):f(void 0)}export{c as SECURITY_SERVICE_PRINCIPALS,h as registerSecurityDelegates};
@@ -5,4 +5,4 @@ import { type IdentityCentreStatus } from "./types.js";
5
5
  * Check if AWS Identity Centre (SSO) is enabled.
6
6
  * Returns the number of SSO instances found.
7
7
  */
8
- export declare function checkIdentityCentreStatus(client: SSOAdminClient): Promise<Result<IdentityCentreStatus>>;
8
+ export declare function checkIdentityCentreStatus(client: SSOAdminClient, abortSignal?: AbortSignal): Promise<Result<IdentityCentreStatus>>;
@@ -1 +1 @@
1
- import{ListInstancesCommand as r}from"@aws-sdk/client-sso-admin";import{success as s,failure as o}from"@fjall/generator";import{getErrorMessage as a}from"@fjall/util";import{SDK_TIMEOUT_MS as c}from"./types.js";async function p(n){try{const e=(await n.send(new r({}),{abortSignal:AbortSignal.timeout(c)})).Instances??[];return s({enabled:e.length>0,instanceCount:e.length})}catch(t){return o(new Error(`Failed to check Identity Centre status: ${a(t)}`))}}export{p as checkIdentityCentreStatus};
1
+ import{ListInstancesCommand as s}from"@aws-sdk/client-sso-admin";import{success as o,failure as a}from"@fjall/generator";import{getErrorMessage as c}from"@fjall/util";import{composeSdkAbortSignal as i}from"./types.js";async function l(n,r){try{const e=(await n.send(new s({}),{abortSignal:i(r)})).Instances??[];return o({enabled:e.length>0,instanceCount:e.length})}catch(t){return a(new Error(`Failed to check Identity Centre status: ${c(t)}`))}}export{l as checkIdentityCentreStatus};
@@ -4,4 +4,4 @@ import { type Result } from "@fjall/generator";
4
4
  * Enable IPAM delegated administrator for the given account.
5
5
  * Idempotent — calling when already delegated is a no-op.
6
6
  */
7
- export declare function enableIpamDelegatedAdmin(client: EC2Client, delegatedAdminAccountId: string): Promise<Result<void>>;
7
+ export declare function enableIpamDelegatedAdmin(client: EC2Client, delegatedAdminAccountId: string, abortSignal?: AbortSignal): Promise<Result<void>>;
@@ -1 +1 @@
1
- import{EnableIpamOrganizationAdminAccountCommand as o}from"@aws-sdk/client-ec2";import{success as a,failure as t}from"@fjall/generator";import{getErrorMessage as i}from"@fjall/util";import{SDK_TIMEOUT_MS as m}from"./types.js";async function d(e,r){try{return await e.send(new o({DryRun:!1,DelegatedAdminAccountId:r}),{abortSignal:AbortSignal.timeout(m)}),a(void 0)}catch(n){return t(new Error(`Failed to enable IPAM delegation for account ${r}: ${i(n)}`))}}export{d as enableIpamDelegatedAdmin};
1
+ import{EnableIpamOrganizationAdminAccountCommand as a}from"@aws-sdk/client-ec2";import{success as t,failure as i}from"@fjall/generator";import{getErrorMessage as m}from"@fjall/util";import{composeSdkAbortSignal as c}from"./types.js";async function u(e,r,n){try{return await e.send(new a({DryRun:!1,DelegatedAdminAccountId:r}),{abortSignal:c(n)}),t(void 0)}catch(o){return i(new Error(`Failed to enable IPAM delegation for account ${r}: ${m(o)}`))}}export{u as enableIpamDelegatedAdmin};
@@ -5,8 +5,8 @@ import { type OrgDetails } from "./types.js";
5
5
  * Ensure an AWS Organisation exists, creating one if necessary.
6
6
  * Idempotent — safe to call when the organisation already exists.
7
7
  */
8
- export declare function ensureOrganisationExists(client: OrganizationsClient): Promise<Result<OrgDetails>>;
8
+ export declare function ensureOrganisationExists(client: OrganizationsClient, abortSignal?: AbortSignal): Promise<Result<OrgDetails>>;
9
9
  /**
10
10
  * Describe the current AWS Organisation, returning null if none exists.
11
11
  */
12
- export declare function describeOrganisation(client: OrganizationsClient): Promise<Result<OrgDetails | null>>;
12
+ export declare function describeOrganisation(client: OrganizationsClient, abortSignal?: AbortSignal): Promise<Result<OrgDetails | null>>;
@@ -1 +1 @@
1
- import{DescribeOrganizationCommand as m,CreateOrganizationCommand as l,ListRootsCommand as f}from"@aws-sdk/client-organizations";import{success as n,failure as o}from"@fjall/generator";import{getErrorMessage as u}from"@fjall/util";import{extractErrorName as d,SDK_TIMEOUT_MS as s,AWS_ERROR_NAMES as I}from"./types.js";async function p(e){try{let r=!1,t=await c(e);if(!t)try{t=(await e.send(new l({FeatureSet:"ALL"}),{abortSignal:AbortSignal.timeout(s)})).Organization??null,r=!0}catch(i){if(d(i)==="AlreadyInOrganizationException"){if(t=await c(e),!t)return o(new Error("Account is already in an organisation but DescribeOrganization returned nothing"))}else throw i}if(!t?.Id||!t.MasterAccountId)return o(new Error("Organisation is missing required fields (Id or MasterAccountId)"));const a=await g(e);return a.success?n({orgId:t.Id,rootId:a.data,managementAccountId:t.MasterAccountId,created:r}):a}catch(r){return o(new Error(`Failed to ensure organisation exists: ${u(r)}`))}}async function S(e){try{const r=await c(e);if(!r)return n(null);if(!r.Id||!r.MasterAccountId)return o(new Error("Organisation is missing required fields (Id or MasterAccountId)"));const t=await g(e);return t.success?n({orgId:r.Id,rootId:t.data,managementAccountId:r.MasterAccountId,created:!1}):t}catch(r){return o(new Error(`Failed to describe organisation: ${u(r)}`))}}async function g(e){const t=(await e.send(new f({}),{abortSignal:AbortSignal.timeout(s)})).Roots??[];return t.length===0||!t[0]?.Id?o(new Error("No organisation root found")):n(t[0].Id)}async function c(e){try{return(await e.send(new m({}),{abortSignal:AbortSignal.timeout(s)})).Organization??null}catch(r){if(d(r)===I.ORGS_NOT_IN_USE)return null;throw r}}export{S as describeOrganisation,p as ensureOrganisationExists};
1
+ import{DescribeOrganizationCommand as f,CreateOrganizationCommand as I,ListRootsCommand as w}from"@aws-sdk/client-organizations";import{success as a,failure as n}from"@fjall/generator";import{getErrorMessage as d}from"@fjall/util";import{extractErrorName as g,composeSdkAbortSignal as c,AWS_ERROR_NAMES as l}from"./types.js";async function y(t,o){try{let r=!1,e=await u(t,o);if(!e)try{e=(await t.send(new I({FeatureSet:"ALL"}),{abortSignal:c(o)})).Organization??null,r=!0}catch(i){if(g(i)==="AlreadyInOrganizationException"){if(e=await u(t,o),!e)return n(new Error("Account is already in an organisation but DescribeOrganization returned nothing"))}else throw i}if(!e?.Id||!e.MasterAccountId)return n(new Error("Organisation is missing required fields (Id or MasterAccountId)"));const s=await m(t,o);return s.success?a({orgId:e.Id,rootId:s.data,managementAccountId:e.MasterAccountId,created:r}):s}catch(r){return n(new Error(`Failed to ensure organisation exists: ${d(r)}`))}}async function N(t,o){try{const r=await u(t,o);if(!r)return a(null);if(!r.Id||!r.MasterAccountId)return n(new Error("Organisation is missing required fields (Id or MasterAccountId)"));const e=await m(t,o);return e.success?a({orgId:r.Id,rootId:e.data,managementAccountId:r.MasterAccountId,created:!1}):e}catch(r){return n(new Error(`Failed to describe organisation: ${d(r)}`))}}async function m(t,o){const e=(await t.send(new w({}),{abortSignal:c(o)})).Roots??[];return e.length===0||!e[0]?.Id?n(new Error("No organisation root found")):a(e[0].Id)}async function u(t,o){try{return(await t.send(new f({}),{abortSignal:c(o)})).Organization??null}catch(r){if(g(r)===l.ORGS_NOT_IN_USE)return null;throw r}}export{N as describeOrganisation,y as ensureOrganisationExists};
@@ -15,7 +15,7 @@ import { type OUMap, type OUTree, type AccountInfo, type AccountPlacementResult
15
15
  * AWS cannot move OUs between parents. Migrating from flat to nested
16
16
  * creates new OUs under new parents; old empty OUs remain at root.
17
17
  */
18
- export declare function ensureOrganisationalUnitsExist(client: OrganizationsClient, rootId: string, ouConfig: string[] | OUTree): Promise<Result<OUMap>>;
18
+ export declare function ensureOrganisationalUnitsExist(client: OrganizationsClient, rootId: string, ouConfig: string[] | OUTree, abortSignal?: AbortSignal): Promise<Result<OUMap>>;
19
19
  /**
20
20
  * Build a flat map of accountName (lowercase) to OU ID from an OUTree
21
21
  * and its resolved OUMap.
@@ -36,4 +36,4 @@ export declare function buildAccountToOUMap(tree: OUTree, ouMap: OUMap, prefix?:
36
36
  *
37
37
  * Idempotent — accounts already in the correct OU are counted but not moved.
38
38
  */
39
- export declare function placeAccountsInOUs(client: OrganizationsClient, ouMap: OUMap, accounts: AccountInfo[], accountToOU?: Record<string, string>): Promise<Result<AccountPlacementResult>>;
39
+ export declare function placeAccountsInOUs(client: OrganizationsClient, ouMap: OUMap, accounts: AccountInfo[], accountToOU?: Record<string, string>, abortSignal?: AbortSignal): Promise<Result<AccountPlacementResult>>;
@@ -1 +1 @@
1
- import{CreateOrganizationalUnitCommand as A,ListOrganizationalUnitsForParentCommand as U,ListParentsCommand as L,MoveAccountCommand as P}from"@aws-sdk/client-organizations";import{success as u,failure as d}from"@fjall/generator";import{extractErrorName as y,isOULeaf as I,SDK_TIMEOUT_MS as w,AWS_ERROR_NAMES as N}from"./types.js";import{accountTier as x,getErrorMessage as g}from"@fjall/util";async function C(r,a){const t=await r.send(new U({ParentId:a}),{abortSignal:AbortSignal.timeout(w)});let e=t.OrganizationalUnits??[],n=t.NextToken;for(;n;){const o=await r.send(new U({ParentId:a,NextToken:n}),{abortSignal:AbortSignal.timeout(w)});e=e.concat(o.OrganizationalUnits??[]),n=o.NextToken}return e}async function E(r,a){try{return(await r.send(new L({ChildId:a}),{abortSignal:AbortSignal.timeout(w)})).Parents?.[0]?.Id}catch(t){if(y(t)===N.CHILD_NOT_FOUND)return;throw t}}async function b(r,a,t,e){const n=e.find(o=>o.Name===t);if(n?.Id)return u(n.Id);try{const s=(await r.send(new A({Name:t,ParentId:a}),{abortSignal:AbortSignal.timeout(w)})).OrganizationalUnit;return s?.Id?u(s.Id):d(new Error(`OU "${t}" was created but has no ID`))}catch(o){return d(new Error(`Failed to create OU "${t}": ${g(o)}`))}}function S(r){return r.charAt(0).toUpperCase()+r.slice(1)}async function T(r,a,t){const e={};if(t.length===0)return u(e);const n=await C(r,a);for(const o of t){const s=S(o),i=await b(r,a,s,n);if(!i.success)return d(i.error);e[o.toLowerCase()]=i.data,n.push({Id:i.data,Name:s})}return u(e)}async function p(r,a,t,e,n,o){const s=await C(r,a);for(const[i,c]of Object.entries(t)){const f=S(i),m=await b(r,a,f,s);if(!m.success)return d(m.error);const O=m.data,h=n?`${n}.${i.toLowerCase()}`:i.toLowerCase();if(e[h]=O,n){const l=i.toLowerCase();o.has(l)||(e[l]=O)}if(s.push({Id:O,Name:f}),!I(c)){const l=await p(r,O,c,e,h,o);if(!l.success)return l}}return u(void 0)}async function z(r,a,t){try{if(Array.isArray(t))return await T(r,a,t);const e={},n=new Set(Object.keys(t).map(s=>s.toLowerCase())),o=await p(r,a,t,e,"",n);return o.success?u(e):d(o.error)}catch(e){return d(new Error(`Failed to ensure OUs exist: ${g(e)}`))}}function $(r,a,t=""){const e={};for(const[n,o]of Object.entries(r)){const s=t?`${t}.${n.toLowerCase()}`:n.toLowerCase(),i=a[s];if(I(o)){if(i)for(const c of o)e[c.toLowerCase()]=i}else Object.assign(e,$(o,a,s))}return e}async function M(r,a,t,e){try{if(t.length===0)return u({moved:0,alreadyPlaced:0});let n=0,o=0;for(const s of t){if(x(s)==="organisation")continue;const i=e?e[s.name.toLowerCase()]:a[s.environment.toLowerCase()];if(!i)continue;const c=await E(r,s.id);if(c){if(c===i){o++;continue}try{await r.send(new P({AccountId:s.id,SourceParentId:c,DestinationParentId:i}),{abortSignal:AbortSignal.timeout(w)}),n++}catch(f){if(y(f)===N.ACCOUNT_NOT_FOUND)continue;throw f}}}return u({moved:n,alreadyPlaced:o})}catch(n){return d(new Error(`Failed to place accounts in OUs: ${g(n)}`))}}export{$ as buildAccountToOUMap,z as ensureOrganisationalUnitsExist,M as placeAccountsInOUs};
1
+ import{CreateOrganizationalUnitCommand as b,ListOrganizationalUnitsForParentCommand as C,ListParentsCommand as x,MoveAccountCommand as $}from"@aws-sdk/client-organizations";import{success as d,failure as u}from"@fjall/generator";import{extractErrorName as I,isOULeaf as p,composeSdkAbortSignal as m,isAborted as y,AWS_ERROR_NAMES as A}from"./types.js";import{accountTier as F,getErrorMessage as U}from"@fjall/util";async function g(n,a,e){const t=await n.send(new C({ParentId:a}),{abortSignal:m(e)});let r=t.OrganizationalUnits??[],o=t.NextToken;for(;o;){if(y(e))throw new Error("Aborted: OU listing cancelled by shutdown signal");const s=await n.send(new C({ParentId:a,NextToken:o}),{abortSignal:m(e)});r=r.concat(s.OrganizationalUnits??[]),o=s.NextToken}return r}async function T(n,a,e){try{return(await n.send(new x({ChildId:a}),{abortSignal:m(e)})).Parents?.[0]?.Id}catch(t){if(I(t)===A.CHILD_NOT_FOUND)return;throw t}}async function L(n,a,e,t,r){const o=t.find(s=>s.Name===e);if(o?.Id)return d(o.Id);try{const c=(await n.send(new b({Name:e,ParentId:a}),{abortSignal:m(r)})).OrganizationalUnit;return c?.Id?d(c.Id):u(new Error(`OU "${e}" was created but has no ID`))}catch(s){return u(new Error(`Failed to create OU "${e}": ${U(s)}`))}}function P(n){return n.charAt(0).toUpperCase()+n.slice(1)}async function k(n,a,e,t){const r={};if(e.length===0)return d(r);const o=await g(n,a,t);for(const s of e){const c=P(s),i=await L(n,a,c,o,t);if(!i.success)return u(i.error);r[s.toLowerCase()]=i.data,o.push({Id:i.data,Name:c})}return d(r)}async function E(n,a,e,t,r,o,s){const c=await g(n,a,s);for(const[i,f]of Object.entries(e)){const w=P(i),O=await L(n,a,w,c,s);if(!O.success)return u(O.error);const h=O.data,N=r?`${r}.${i.toLowerCase()}`:i.toLowerCase();if(t[N]=h,r){const l=i.toLowerCase();o.has(l)||(t[l]=h)}if(c.push({Id:h,Name:w}),!p(f)){const l=await E(n,h,f,t,N,o,s);if(!l.success)return l}}return d(void 0)}async function v(n,a,e,t){if(y(t))return u(new Error("Aborted: OU reconciliation cancelled by shutdown signal"));try{if(Array.isArray(e))return await k(n,a,e,t);const r={},o=new Set(Object.keys(e).map(c=>c.toLowerCase())),s=await E(n,a,e,r,"",o,t);return s.success?d(r):u(s.error)}catch(r){return u(new Error(`Failed to ensure OUs exist: ${U(r)}`))}}function _(n,a,e=""){const t={};for(const[r,o]of Object.entries(n)){const s=e?`${e}.${r.toLowerCase()}`:r.toLowerCase(),c=a[s];if(p(o)){if(c)for(const i of o)t[i.toLowerCase()]=c}else Object.assign(t,_(o,a,s))}return t}async function M(n,a,e,t,r){try{if(e.length===0)return d({moved:0,alreadyPlaced:0});let o=0,s=0;for(const c of e){if(y(r))return u(new Error("Aborted: account placement cancelled by shutdown signal"));if(F(c)==="organisation")continue;const i=t?t[c.name.toLowerCase()]:a[c.environment.toLowerCase()];if(!i)continue;const f=await T(n,c.id,r);if(f){if(f===i){s++;continue}try{await n.send(new $({AccountId:c.id,SourceParentId:f,DestinationParentId:i}),{abortSignal:m(r)}),o++}catch(w){if(I(w)===A.ACCOUNT_NOT_FOUND)continue;throw w}}}return d({moved:o,alreadyPlaced:s})}catch(o){return u(new Error(`Failed to place accounts in OUs: ${U(o)}`))}}export{_ as buildAccountToOUMap,v as ensureOrganisationalUnitsExist,M as placeAccountsInOUs};
@@ -1 +1 @@
1
- import{EnablePolicyTypeCommand as m,ListRootsCommand as P,PolicyType as i}from"@aws-sdk/client-organizations";import{success as _,failure as d}from"@fjall/generator";import{getErrorMessage as w}from"@fjall/util";import{extractErrorName as L,SDK_TIMEOUT_MS as y}from"./types.js";import{sleepAbortable as b}from"../../util/sleepAbortable.js";const E=[i.SERVICE_CONTROL_POLICY,i.TAG_POLICY,i.BACKUP_POLICY,i.AISERVICES_OPT_OUT_POLICY],u=2e3,I=6e4;async function h(r,n,o={}){try{for(const t of E)try{await r.send(new m({RootId:n,PolicyType:t}),{abortSignal:AbortSignal.timeout(y)})}catch(a){if(L(a)==="PolicyTypeAlreadyEnabledException")continue;throw a}return await S(r,n,o),_(void 0)}catch(t){return d(new Error(`Failed to enable policy types: ${w(t)}`))}}async function S(r,n,o){const t=o.pollIntervalMs??u,a=o.pollTimeoutMs??I,s=new Set(E),T=Date.now();for(;;){const f=(await r.send(new P({}),{abortSignal:AbortSignal.timeout(y)})).Roots?.find(e=>e.Id===n),l=new Set;for(const e of f?.PolicyTypes??[])e.Status==="ENABLED"&&e.Type&&l.add(e.Type);const c=[...s].filter(e=>!l.has(e));if(c.length===0)return;const p=Date.now()-T;if(p>=a)throw new Error(`Policy types still PENDING_ENABLE after ${p}ms: ${c.join(", ")}`);if(t>0&&(await b(t,o.abortSignal),o.abortSignal?.aborted===!0))throw new Error("Aborted while waiting for policy types to enable")}}export{h as enablePolicyTypes};
1
+ import{EnablePolicyTypeCommand as m,ListRootsCommand as w,PolicyType as i}from"@aws-sdk/client-organizations";import{success as T,failure as p}from"@fjall/generator";import{getErrorMessage as P}from"@fjall/util";import{extractErrorName as _,composeSdkAbortSignal as E,isAborted as L}from"./types.js";import{sleepAbortable as S}from"../../util/sleepAbortable.js";const d=[i.SERVICE_CONTROL_POLICY,i.TAG_POLICY,i.BACKUP_POLICY,i.AISERVICES_OPT_OUT_POLICY],u=2e3,I=6e4;async function Y(t,n,e={}){try{for(const r of d){if(L(e.abortSignal))return p(new Error("Aborted: policy-type enablement cancelled by shutdown signal"));try{await t.send(new m({RootId:n,PolicyType:r}),{abortSignal:E(e.abortSignal)})}catch(a){if(_(a)==="PolicyTypeAlreadyEnabledException")continue;throw a}}return await g(t,n,e),T(void 0)}catch(r){return p(new Error(`Failed to enable policy types: ${P(r)}`))}}async function g(t,n,e){const r=e.pollIntervalMs??u,a=e.pollTimeoutMs??I,l=new Set(d),f=Date.now();for(;;){const b=(await t.send(new w({}),{abortSignal:E(e.abortSignal)})).Roots?.find(o=>o.Id===n),s=new Set;for(const o of b?.PolicyTypes??[])o.Status==="ENABLED"&&o.Type&&s.add(o.Type);const c=[...l].filter(o=>!s.has(o));if(c.length===0)return;const y=Date.now()-f;if(y>=a)throw new Error(`Policy types still PENDING_ENABLE after ${y}ms: ${c.join(", ")}`);if(r>0&&(await S(r,e.abortSignal),e.abortSignal?.aborted===!0))throw new Error("Aborted while waiting for policy types to enable")}}export{Y as enablePolicyTypes};
@@ -4,4 +4,4 @@ import { type Result } from "@fjall/generator";
4
4
  * Enable RAM sharing with the AWS Organisation.
5
5
  * Idempotent — calling when already enabled is a no-op.
6
6
  */
7
- export declare function enableRamSharing(client: RAMClient): Promise<Result<void>>;
7
+ export declare function enableRamSharing(client: RAMClient, abortSignal?: AbortSignal): Promise<Result<void>>;
@@ -1 +1 @@
1
- import{EnableSharingWithAwsOrganizationCommand as n}from"@aws-sdk/client-ram";import{success as a,failure as o}from"@fjall/generator";import{getErrorMessage as t}from"@fjall/util";import{SDK_TIMEOUT_MS as i}from"./types.js";async function l(r){try{return await r.send(new n({}),{abortSignal:AbortSignal.timeout(i)}),a(void 0)}catch(e){return o(new Error(`Failed to enable RAM sharing: ${t(e)}`))}}export{l as enableRamSharing};
1
+ import{EnableSharingWithAwsOrganizationCommand as a}from"@aws-sdk/client-ram";import{success as o,failure as i}from"@fjall/generator";import{getErrorMessage as t}from"@fjall/util";import{composeSdkAbortSignal as m}from"./types.js";async function l(r,e){try{return await r.send(new a({}),{abortSignal:m(e)}),o(void 0)}catch(n){return i(new Error(`Failed to enable RAM sharing: ${t(n)}`))}}export{l as enableRamSharing};
@@ -1,3 +1,3 @@
1
- import{ListOrganizationsFeaturesCommand as A,EnableOrganizationsRootCredentialsManagementCommand as C,EnableOrganizationsRootSessionsCommand as h}from"@aws-sdk/client-iam";import{success as R,failure as s}from"@fjall/generator";import{composeSdkAbortSignal as d,extractErrorName as u,isAccessDenied as E}from"./types.js";import{getErrorMessage as b,maskSensitiveOutput as p}from"@fjall/util";const w=["RootCredentialsManagement","RootSessions"],y={RootCredentialsManagement:{CommandClass:C,permission:"iam:EnableOrganizationsRootCredentialsManagement"},RootSessions:{CommandClass:h,permission:"iam:EnableOrganizationsRootSessions"}};class o extends Error{partialSummary;constructor(t,e){super(t),this.name="RootAccessEnablementError",this.partialSummary=e}}function g(a){return new o("Trusted access for iam.amazonaws.com is not enabled in Organizations. Re-run organisation setup \u2014 its enable-service-access phase enables it \u2014 or enable it manually (organizations:EnableAWSServiceAccess for iam.amazonaws.com) and retry enabling centralised root access.",a)}async function F(a,t){const e={enabled:[],alreadyEnabled:[]};let c;try{const n=await a.send(new A({}),{abortSignal:d(t)});c=new Set(n.EnabledFeatures??[])}catch(n){const i=u(n);return E(i)?s(new o("Access denied when listing organisation root-access features. Ensure your credentials have iam:ListOrganizationsFeatures permission.",e)):i==="ServiceAccessNotEnabledException"?s(g(e)):s(new o(`Failed to list organisation root-access features: ${p(b(n))}`,e))}const r=[];for(const n of w){if(c.has(n)){e.alreadyEnabled.push(n);continue}const{CommandClass:i,permission:f}=y[n];try{await a.send(new i({}),{abortSignal:d(t)}),e.enabled.push(n)}catch(l){const m=u(l);if(E(m)){const S=r.length>0?`${r.join("; ")}; `:"";return s(new o(`${S}Access denied when enabling ${n}. Ensure your credentials have ${f} permission.`,e))}if(m==="ServiceAccessNotEnabledException")return s(g(e));r.push(`${n}: ${p(b(l))}`)}}return r.length>0?s(new o(`Failed to enable centralised root access:
2
- ${r.join(`
3
- `)}`,e)):R(e)}export{w as ROOT_ACCESS_FEATURES,o as RootAccessEnablementError,F as enableCentralisedRootAccess};
1
+ import{ListOrganizationsFeaturesCommand as h,EnableOrganizationsRootCredentialsManagementCommand as S,EnableOrganizationsRootSessionsCommand as w}from"@aws-sdk/client-iam";import{success as C,failure as a}from"@fjall/generator";import{composeSdkAbortSignal as d,extractErrorName as u,isAborted as R,isAccessDenied as E}from"./types.js";import{getErrorMessage as b,maskSensitiveOutput as p}from"@fjall/util";const y=["RootCredentialsManagement","RootSessions"],z={RootCredentialsManagement:{CommandClass:S,permission:"iam:EnableOrganizationsRootCredentialsManagement"},RootSessions:{CommandClass:w,permission:"iam:EnableOrganizationsRootSessions"}};class r extends Error{partialSummary;constructor(t,e){super(t),this.name="RootAccessEnablementError",this.partialSummary=e}}function g(o){return new r("Trusted access for iam.amazonaws.com is not enabled in Organizations. Re-run organisation setup \u2014 its enable-service-access phase enables it \u2014 or enable it manually (organizations:EnableAWSServiceAccess for iam.amazonaws.com) and retry enabling centralised root access.",o)}async function F(o,t){const e={enabled:[],alreadyEnabled:[]};let l;try{const n=await o.send(new h({}),{abortSignal:d(t)});l=new Set(n.EnabledFeatures??[])}catch(n){const i=u(n);return E(i)?a(new r("Access denied when listing organisation root-access features. Ensure your credentials have iam:ListOrganizationsFeatures permission.",e)):i==="ServiceAccessNotEnabledException"?a(g(e)):a(new r(`Failed to list organisation root-access features: ${p(b(n))}`,e))}const s=[];for(const n of y){if(R(t)){const c=s.length>0?`${s.join("; ")}; `:"";return a(new r(`${c}Aborted: centralised root-access enablement cancelled by shutdown signal.`,e))}if(l.has(n)){e.alreadyEnabled.push(n);continue}const{CommandClass:i,permission:f}=z[n];try{await o.send(new i({}),{abortSignal:d(t)}),e.enabled.push(n)}catch(c){const m=u(c);if(E(m)){const A=s.length>0?`${s.join("; ")}; `:"";return a(new r(`${A}Access denied when enabling ${n}. Ensure your credentials have ${f} permission.`,e))}if(m==="ServiceAccessNotEnabledException")return a(g(e));s.push(`${n}: ${p(b(c))}`)}}return s.length>0?a(new r(`Failed to enable centralised root access:
2
+ ${s.join(`
3
+ `)}`,e)):C(e)}export{y as ROOT_ACCESS_FEATURES,r as RootAccessEnablementError,F as enableCentralisedRootAccess};
@@ -10,4 +10,4 @@ export declare const SERVICE_PRINCIPALS: readonly ["account.amazonaws.com", "sso
10
10
  * Enable AWS service access for all required service principals.
11
11
  * Idempotent — enabling an already-enabled principal is a no-op.
12
12
  */
13
- export declare function enableServiceAccess(client: OrganizationsClient): Promise<Result<void>>;
13
+ export declare function enableServiceAccess(client: OrganizationsClient, abortSignal?: AbortSignal): Promise<Result<void>>;
@@ -1 +1 @@
1
- import{EnableAWSServiceAccessCommand as n}from"@aws-sdk/client-organizations";import{success as s,failure as o}from"@fjall/generator";import{extractErrorName as m,SDK_TIMEOUT_MS as i,AWS_ERROR_NAMES as t}from"./types.js";import{getErrorMessage as e}from"@fjall/util";const w=["account.amazonaws.com","sso.amazonaws.com","ipam.amazonaws.com","ram.amazonaws.com","backup.amazonaws.com","member.org.stacksets.cloudformation.amazonaws.com","guardduty.amazonaws.com","securityhub.amazonaws.com","config.amazonaws.com","inspector2.amazonaws.com","access-analyzer.amazonaws.com","cloudtrail.amazonaws.com","iam.amazonaws.com"];async function z(c){try{for(const a of w)try{await c.send(new n({ServicePrincipal:a}),{abortSignal:AbortSignal.timeout(i)})}catch(r){if(m(r)===t.ACCESS_DENIED)return o(new Error(`Access denied when enabling service access for ${a}. Ensure your credentials have organizations:EnableAWSServiceAccess permission.`));throw new Error(`Service principal ${a}: ${e(r)}`,{cause:r})}return s(void 0)}catch(a){return o(new Error(`Failed to enable service access: ${e(a)}`))}}export{w as SERVICE_PRINCIPALS,z as enableServiceAccess};
1
+ import{EnableAWSServiceAccessCommand as s}from"@aws-sdk/client-organizations";import{success as m,failure as e}from"@fjall/generator";import{extractErrorName as i,composeSdkAbortSignal as t,isAborted as w,AWS_ERROR_NAMES as l}from"./types.js";import{getErrorMessage as c}from"@fjall/util";const u=["account.amazonaws.com","sso.amazonaws.com","ipam.amazonaws.com","ram.amazonaws.com","backup.amazonaws.com","member.org.stacksets.cloudformation.amazonaws.com","guardduty.amazonaws.com","securityhub.amazonaws.com","config.amazonaws.com","inspector2.amazonaws.com","access-analyzer.amazonaws.com","cloudtrail.amazonaws.com","iam.amazonaws.com"];async function f(n,o){try{for(const a of u){if(w(o))return e(new Error("Aborted: service-access enablement cancelled by shutdown signal"));try{await n.send(new s({ServicePrincipal:a}),{abortSignal:t(o)})}catch(r){if(i(r)===l.ACCESS_DENIED)return e(new Error(`Access denied when enabling service access for ${a}. Ensure your credentials have organizations:EnableAWSServiceAccess permission.`));throw new Error(`Service principal ${a}: ${c(r)}`,{cause:r})}}return m(void 0)}catch(a){return e(new Error(`Failed to enable service access: ${c(a)}`))}}export{u as SERVICE_PRINCIPALS,f as enableServiceAccess};
@@ -4,4 +4,4 @@ import { type Result } from "@fjall/generator";
4
4
  * Activate trusted access for CloudFormation StackSets.
5
5
  * Idempotent — calling when already activated is a no-op.
6
6
  */
7
- export declare function activateTrustedAccess(client: CloudFormationClient): Promise<Result<void>>;
7
+ export declare function activateTrustedAccess(client: CloudFormationClient, abortSignal?: AbortSignal): Promise<Result<void>>;
@@ -1 +1 @@
1
- import{ActivateOrganizationsAccessCommand as e}from"@aws-sdk/client-cloudformation";import{success as a,failure as o}from"@fjall/generator";import{getErrorMessage as i}from"@fjall/util";import{SDK_TIMEOUT_MS as s}from"./types.js";async function d(r){try{return await r.send(new e({}),{abortSignal:AbortSignal.timeout(s)}),a(void 0)}catch(t){return o(new Error(`Failed to activate trusted access: ${i(t)}`))}}export{d as activateTrustedAccess};
1
+ import{ActivateOrganizationsAccessCommand as o}from"@aws-sdk/client-cloudformation";import{success as a,failure as s}from"@fjall/generator";import{getErrorMessage as c}from"@fjall/util";import{composeSdkAbortSignal as i}from"./types.js";async function f(r,t){try{return await r.send(new o({}),{abortSignal:i(t)}),a(void 0)}catch(e){return s(new Error(`Failed to activate trusted access: ${c(e)}`))}}export{f as activateTrustedAccess};
@@ -54,8 +54,13 @@ export declare function isOULeaf(value: string[] | OUTree): value is string[];
54
54
  * Compose the per-request SDK timeout with an optional caller shutdown
55
55
  * signal so SIGTERM is not stalled by an in-flight call (the
56
56
  * `DeployParams.abortSignal` contract).
57
+ *
58
+ * `timeoutMs` defaults to {@link SDK_TIMEOUT_MS}; pass a per-call budget
59
+ * (e.g. a DescribeSecret or AssumeRole timeout) for SDK calls that carry
60
+ * their own. With no caller signal it returns the bare timeout unchanged,
61
+ * so timeout-identity test pins stay valid.
57
62
  */
58
- export declare function composeSdkAbortSignal(abortSignal?: AbortSignal): AbortSignal;
63
+ export declare function composeSdkAbortSignal(abortSignal?: AbortSignal, timeoutMs?: number): AbortSignal;
59
64
  export declare function isAborted(abortSignal?: AbortSignal): boolean;
60
65
  /**
61
66
  * Extract the error name from an AWS SDK error.
@@ -1 +1 @@
1
- const o=3e4,e={ACCESS_DENIED:"AccessDeniedException",ACCOUNT_ALREADY_REGISTERED:"AccountAlreadyRegisteredException",ACCOUNT_NOT_FOUND:"AccountNotFoundException",CHILD_NOT_FOUND:"ChildNotFoundException",ORGS_NOT_IN_USE:"AWSOrganizationsNotInUseException"};function i(n){return Array.isArray(n)}function c(n){const t=AbortSignal.timeout(3e4);return n!==void 0?AbortSignal.any([n,t]):t}function r(n){return n?.aborted===!0}function E(n){return typeof n=="object"&&n!==null&&"name"in n&&typeof n.name=="string"?n.name:"UnknownError"}function A(n){return n==="AccessDenied"||n===e.ACCESS_DENIED}export{e as AWS_ERROR_NAMES,o as SDK_TIMEOUT_MS,c as composeSdkAbortSignal,E as extractErrorName,r as isAborted,A as isAccessDenied,i as isOULeaf};
1
+ const i=3e4,o={ACCESS_DENIED:"AccessDeniedException",ACCOUNT_ALREADY_REGISTERED:"AccountAlreadyRegisteredException",ACCOUNT_NOT_FOUND:"AccountNotFoundException",CHILD_NOT_FOUND:"ChildNotFoundException",ORGS_NOT_IN_USE:"AWSOrganizationsNotInUseException"};function c(n){return Array.isArray(n)}function r(n,e=3e4){const t=AbortSignal.timeout(e);return n!==void 0?AbortSignal.any([n,t]):t}function E(n){return n?.aborted===!0}function A(n){return typeof n=="object"&&n!==null&&"name"in n&&typeof n.name=="string"?n.name:"UnknownError"}function u(n){return n==="AccessDenied"||n===o.ACCESS_DENIED}export{o as AWS_ERROR_NAMES,i as SDK_TIMEOUT_MS,r as composeSdkAbortSignal,A as extractErrorName,E as isAborted,u as isAccessDenied,c as isOULeaf};
@@ -49,6 +49,8 @@ export { decideNextTransition, reconcileTrailMigration, decommissionMemberTrailS
49
49
  export type { MemberTrailFacts, TrailMigrationTransition, TrailMigrationOutcome, DecommissionClients, DecommissionInput, DecommissionOutcome } from "./orchestration/index.js";
50
50
  export { unlockBucket, unlockQueue } from "./orchestration/index.js";
51
51
  export type { UnlockBucketInput, UnlockBucketReport, UnlockQueueInput, UnlockQueueReport } from "./orchestration/index.js";
52
+ export { triageBucketPolicy, isEnforceSslStatement, toResourcePolicyStatements, restoreBucketPolicy, synthesiseEnforceSslDocument, ensureEnforceSsl, restoreAndReconcileQuarantinedBucket } from "./orchestration/index.js";
53
+ export type { BucketPolicyTriage, ClassifiedStatement, StatementClassification, RawPolicyStatement, RawPolicyDocument, ConvertedResourcePolicy, RestoreBucketPolicyInput, RestoreBucketPolicySuccess, RestoreBucketPolicyError, RestoreAndReconcileInput, RestoreAndReconcileReport, ReconcileOutcome } from "./orchestration/index.js";
52
54
  export { verifyOrgTrailDelivery } from "./aws/cloudtrail/orgTrailDelivery.js";
53
55
  export type { OrgTrailDeliveryReport, OrgTrailAccountDelivery } from "./aws/cloudtrail/orgTrailDelivery.js";
54
56
  export { assumeRootForTask, isRootTaskPolicyArn, ROOT_TASK_POLICY_ARNS, MAX_ROOT_SESSION_SECONDS } from "./aws/sts/assumeRoot.js";
package/dist/src/index.js CHANGED
@@ -1 +1 @@
1
- import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as i,DEPLOYMENT_EVENT_STATUS_REASON_MAX as a,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as s,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as E,CASCADE_PHASES as n,CASCADE_ACCOUNT_STATUSES as c}from"./types/index.js";import{SimpleAwsProvider as A}from"./aws/index.js";import{checkTargetReadiness as T,describeTargetReadinessReason as p,buildTargetUnreadyAdvisory as _,buildTargetSetUnreadyAdvisory as O}from"./aws/index.js";import{ensureOrganisationExists as R,describeOrganisation as m,enablePolicyTypes as d,enableServiceAccess as P,SERVICE_PRINCIPALS as C,enableRamSharing as N,activateTrustedAccess as f,enableIpamDelegatedAdmin as g,updateBackupGlobalSettings as x,listAccounts as D,findAccount as I,createAccount as L,ensureOrganisationalUnitsExist as U,placeAccountsInOUs as k,buildAccountToOUMap as y,activateCostAllocationTags as v,checkIdentityCentreStatus as M,extractErrorName as F,isOULeaf as Y,registerSecurityDelegates as b,SECURITY_SERVICE_PRINCIPALS as B,enableCentralisedRootAccess as h,ROOT_ACCESS_FEATURES as K,RootAccessEnablementError as G}from"./aws/index.js";import{STEP_IDS as H,STEP_NAMES as X,INFRASTRUCTURE_STEP_NAMES as w,INFRA_STEP_NAME as j}from"./types/index.js";import{ProgressReporter as q,APPLICATION_STACKS as z,ORGANISATION_TYPES as J,APPLICATION_DEPLOY_ORDER as W,APPLICATION_DESTROY_ORDER as Z,OPENNEXT_DEPLOY_ORDER as $,OPENNEXT_DESTROY_ORDER as ee,PARALLEL_DEPLOY_GROUPS as re,PARALLEL_DESTROY_GROUPS as te,OPENNEXT_PARALLEL_GROUPS as oe,PARALLEL_OPERATION_TYPES as ie,isApplicationOperation as ae,isOrganisationOperation as se,getParallelDeployGroups as Ee,getParallelDestroyGroups as ne,getApplicationDeployOrder as ce,getApplicationDestroyOrder as Se,getApplicationStackName as Ae,getOrganisationStackName as le,isApplicationStack as Te,isQuarantineDetail as pe,isRetainedBucketsDetail as _e,getApplicationStepName as Oe,getApplicationStepId as ue,toPascalCase as Re,isOpenNextPattern as me,OPENNEXT_PATTERNS as de,deriveResourcesFromManifestStacks as Pe,STACK_NOT_FOUND_PATTERN as Ce,STACK_FAILED_STATE_PATTERN as Ne,CDK_NO_STACKS_MATCH as fe,INFRASTRUCTURE_FILENAME as ge,ApplicationError as xe,wrapApplicationError as De,FjallStateFileSchema as Ie,readStateFile as Le,writeStateFile as Ue,createEmptyState as ke,deleteStateFile as ye,updateTemplateHash as ve,getStateFilePath as Me,stubCallerIdentity as Fe}from"./types/index.js";import{CloudFormationEventMonitor as be}from"./aws/index.js";import{CdkService as he,CdkArgumentBuilder as Ke,CdkProcessManager as Ge,CdkEventMonitor as Ve,startStackMonitoring as He,DEFAULT_DEPLOY_TIMEOUT_MS as Xe,isCdkError as we,formatInfrastructureError as je,getStructuralHint as Qe,getSourceContext as qe,hasCdkDifferences as ze,parseDiffOutput as Je,CloudFormationService as We,CloudFormationError as Ze,EcsService as $e,EcsError as er,EcsServiceResolver as rr,TemplateHashService as tr,TemplateHashError as or,CdkContextBuilder as ir,emitProgress as ar,PROGRESS_MESSAGES as sr,parseBuildPhase as Er,buildStepContextBuildConfig as nr,convertCloudFormationOutputsToRecord as cr,ApplicationStackService as Sr}from"./services/index.js";import{CdkError as lr}from"./types/errors/index.js";import{BaseServiceError as pr,ValidationError as _r,AuthError as Or,AwsError as ur,DeploymentError as Rr,NetworkError as mr,FileSystemError as dr,ConfigError as Pr,toServiceError as Cr}from"./types/errors/index.js";import{filterDangerousEnvVars as fr,maskSensitiveOutput as gr,parseShellArgs as xr,sleep as Dr}from"@fjall/util";import{hasDockerfile as Lr}from"./util/dockerfileDetection.js";import{createSequencedCallbacks as kr}from"./util/sequencedCallbacks.js";import{fileExists as vr}from"@fjall/util/fsHelpers";import{success as Fr,failure as Yr,isSuccess as br,isFailure as Br}from"@fjall/generator";import{deploy as Kr}from"./orchestration/index.js";import{destroy as Vr}from"./orchestration/index.js";import{partitionAccounts as Xr}from"./orchestration/index.js";import{buildRegionList as jr,buildAccountRegionPairs as Qr,cascadeHomeRegion as qr,cascadeOperationKey as zr}from"./orchestration/index.js";import{projectScalarSummary as Wr,projectAccountRows as Zr}from"./orchestration/index.js";import{reconcileProviderAccounts as et,mergeReconciledProviderAccounts as rt}from"./orchestration/index.js";import{decideNextTransition as ot,reconcileTrailMigration as it,decommissionMemberTrailStorage as at,ORG_TRAIL_BUCKET_OUTPUT_KEY as st,TRAIL_BUCKET_OUTPUT_KEY as Et,TRAIL_KEY_ARN_OUTPUT_KEY as nt}from"./orchestration/index.js";import{unlockBucket as St,unlockQueue as At}from"./orchestration/index.js";import{verifyOrgTrailDelivery as Tt}from"./aws/cloudtrail/orgTrailDelivery.js";import{assumeRootForTask as _t,isRootTaskPolicyArn as Ot,ROOT_TASK_POLICY_ARNS as ut,MAX_ROOT_SESSION_SECONDS as Rt}from"./aws/sts/assumeRoot.js";import{parseAccountsConfiguration as dt,flattenAccountsToEnvironments as Pt,extractAllAccountNames as Ct,accountsConfigToOUTree as Nt,isStringArray as ft,isAccountsConfig as gt,isOuOnlyAccountBucket as xt,OU_ONLY_ACCOUNT_BUCKETS as Dt}from"./orchestration/index.js";import{runOpenNextBuild as Lt}from"./orchestration/index.js";import{runOrganisationSetup as kt,ORG_SETUP_PHASES as yt}from"./orchestration/index.js";import{FrameworkRegistry as Mt}from"./orchestration/index.js";import{openNextBuilder as Yt,dockerBuilder as bt}from"./orchestration/index.js";import{StepRegistry as ht,getDestroyStepId as Kt}from"./steps/index.js";export{W as APPLICATION_DEPLOY_ORDER,Z as APPLICATION_DESTROY_ORDER,z as APPLICATION_STACKS,xe as ApplicationError,Sr as ApplicationStackService,Or as AuthError,ur as AwsError,pr as BaseServiceError,c as CASCADE_ACCOUNT_STATUSES,n as CASCADE_PHASES,fe as CDK_NO_STACKS_MATCH,Ke as CdkArgumentBuilder,ir as CdkContextBuilder,lr as CdkError,Ve as CdkEventMonitor,Ge as CdkProcessManager,he as CdkService,Ze as CloudFormationError,be as CloudFormationEventMonitor,We as CloudFormationService,Pr as ConfigError,Xe as DEFAULT_DEPLOY_TIMEOUT_MS,s as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,i as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,a as DEPLOYMENT_EVENT_STATUS_REASON_MAX,E as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,o as DEPLOYMENT_EVENT_TYPES,Rr as DeploymentError,t as DeploymentEventSchema,er as EcsError,$e as EcsService,rr as EcsServiceResolver,dr as FileSystemError,Ie as FjallStateFileSchema,Mt as FrameworkRegistry,ge as INFRASTRUCTURE_FILENAME,w as INFRASTRUCTURE_STEP_NAMES,j as INFRA_STEP_NAME,Rt as MAX_ROOT_SESSION_SECONDS,mr as NetworkError,$ as OPENNEXT_DEPLOY_ORDER,ee as OPENNEXT_DESTROY_ORDER,oe as OPENNEXT_PARALLEL_GROUPS,de as OPENNEXT_PATTERNS,J as ORGANISATION_TYPES,yt as ORG_SETUP_PHASES,st as ORG_TRAIL_BUCKET_OUTPUT_KEY,Dt as OU_ONLY_ACCOUNT_BUCKETS,re as PARALLEL_DEPLOY_GROUPS,te as PARALLEL_DESTROY_GROUPS,ie as PARALLEL_OPERATION_TYPES,sr as PROGRESS_MESSAGES,q as ProgressReporter,K as ROOT_ACCESS_FEATURES,ut as ROOT_TASK_POLICY_ARNS,G as RootAccessEnablementError,B as SECURITY_SERVICE_PRINCIPALS,C as SERVICE_PRINCIPALS,Ne as STACK_FAILED_STATE_PATTERN,Ce as STACK_NOT_FOUND_PATTERN,H as STEP_IDS,X as STEP_NAMES,A as SimpleAwsProvider,ht as StepRegistry,Et as TRAIL_BUCKET_OUTPUT_KEY,nt as TRAIL_KEY_ARN_OUTPUT_KEY,or as TemplateHashError,tr as TemplateHashService,_r as ValidationError,Nt as accountsConfigToOUTree,v as activateCostAllocationTags,f as activateTrustedAccess,_t as assumeRootForTask,Qr as buildAccountRegionPairs,y as buildAccountToOUMap,jr as buildRegionList,nr as buildStepContextBuildConfig,O as buildTargetSetUnreadyAdvisory,_ as buildTargetUnreadyAdvisory,qr as cascadeHomeRegion,zr as cascadeOperationKey,M as checkIdentityCentreStatus,T as checkTargetReadiness,cr as convertCloudFormationOutputsToRecord,L as createAccount,ke as createEmptyState,kr as createSequencedCallbacks,ot as decideNextTransition,at as decommissionMemberTrailStorage,ye as deleteStateFile,Kr as deploy,Pe as deriveResourcesFromManifestStacks,m as describeOrganisation,p as describeTargetReadinessReason,Vr as destroy,bt as dockerBuilder,ar as emitProgress,h as enableCentralisedRootAccess,g as enableIpamDelegatedAdmin,d as enablePolicyTypes,N as enableRamSharing,P as enableServiceAccess,R as ensureOrganisationExists,U as ensureOrganisationalUnitsExist,Ct as extractAllAccountNames,F as extractErrorName,Yr as failure,vr as fileExists,fr as filterDangerousEnvVars,I as findAccount,Pt as flattenAccountsToEnvironments,je as formatInfrastructureError,ce as getApplicationDeployOrder,Se as getApplicationDestroyOrder,Ae as getApplicationStackName,ue as getApplicationStepId,Oe as getApplicationStepName,Kt as getDestroyStepId,le as getOrganisationStackName,Ee as getParallelDeployGroups,ne as getParallelDestroyGroups,qe as getSourceContext,Me as getStateFilePath,Qe as getStructuralHint,ze as hasCdkDifferences,Lr as hasDockerfile,gt as isAccountsConfig,ae as isApplicationOperation,Te as isApplicationStack,we as isCdkError,Br as isFailure,Y as isOULeaf,me as isOpenNextPattern,se as isOrganisationOperation,xt as isOuOnlyAccountBucket,pe as isQuarantineDetail,_e as isRetainedBucketsDetail,Ot as isRootTaskPolicyArn,ft as isStringArray,br as isSuccess,D as listAccounts,gr as maskSensitiveOutput,rt as mergeReconciledProviderAccounts,Yt as openNextBuilder,dt as parseAccountsConfiguration,Er as parseBuildPhase,Je as parseDiffOutput,xr as parseShellArgs,Xr as partitionAccounts,k as placeAccountsInOUs,Zr as projectAccountRows,Wr as projectScalarSummary,Le as readStateFile,et as reconcileProviderAccounts,it as reconcileTrailMigration,b as registerSecurityDelegates,Lt as runOpenNextBuild,kt as runOrganisationSetup,Dr as sleep,He as startStackMonitoring,Fe as stubCallerIdentity,Fr as success,Re as toPascalCase,Cr as toServiceError,St as unlockBucket,At as unlockQueue,x as updateBackupGlobalSettings,ve as updateTemplateHash,Tt as verifyOrgTrailDelivery,De as wrapApplicationError,Ue as writeStateFile};
1
+ import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as i,DEPLOYMENT_EVENT_STATUS_REASON_MAX as a,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as s,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as n,CASCADE_PHASES as c,CASCADE_ACCOUNT_STATUSES as E}from"./types/index.js";import{SimpleAwsProvider as l}from"./aws/index.js";import{checkTargetReadiness as T,describeTargetReadinessReason as p,buildTargetUnreadyAdvisory as u,buildTargetSetUnreadyAdvisory as _}from"./aws/index.js";import{ensureOrganisationExists as R,describeOrganisation as m,enablePolicyTypes as d,enableServiceAccess as P,SERVICE_PRINCIPALS as C,enableRamSharing as N,activateTrustedAccess as f,enableIpamDelegatedAdmin as g,updateBackupGlobalSettings as x,listAccounts as D,findAccount as I,createAccount as L,ensureOrganisationalUnitsExist as U,placeAccountsInOUs as k,buildAccountToOUMap as y,activateCostAllocationTags as v,checkIdentityCentreStatus as M,extractErrorName as F,isOULeaf as Y,registerSecurityDelegates as b,SECURITY_SERVICE_PRINCIPALS as B,enableCentralisedRootAccess as h,ROOT_ACCESS_FEATURES as K,RootAccessEnablementError as G}from"./aws/index.js";import{STEP_IDS as H,STEP_NAMES as X,INFRASTRUCTURE_STEP_NAMES as w,INFRA_STEP_NAME as j}from"./types/index.js";import{ProgressReporter as q,APPLICATION_STACKS as z,ORGANISATION_TYPES as J,APPLICATION_DEPLOY_ORDER as W,APPLICATION_DESTROY_ORDER as Z,OPENNEXT_DEPLOY_ORDER as $,OPENNEXT_DESTROY_ORDER as ee,PARALLEL_DEPLOY_GROUPS as re,PARALLEL_DESTROY_GROUPS as te,OPENNEXT_PARALLEL_GROUPS as oe,PARALLEL_OPERATION_TYPES as ie,isApplicationOperation as ae,isOrganisationOperation as se,getParallelDeployGroups as ne,getParallelDestroyGroups as ce,getApplicationDeployOrder as Ee,getApplicationDestroyOrder as Se,getApplicationStackName as le,getOrganisationStackName as Ae,isApplicationStack as Te,isQuarantineDetail as pe,isRetainedBucketsDetail as ue,getApplicationStepName as _e,getApplicationStepId as Oe,toPascalCase as Re,isOpenNextPattern as me,OPENNEXT_PATTERNS as de,deriveResourcesFromManifestStacks as Pe,STACK_NOT_FOUND_PATTERN as Ce,STACK_FAILED_STATE_PATTERN as Ne,CDK_NO_STACKS_MATCH as fe,INFRASTRUCTURE_FILENAME as ge,ApplicationError as xe,wrapApplicationError as De,FjallStateFileSchema as Ie,readStateFile as Le,writeStateFile as Ue,createEmptyState as ke,deleteStateFile as ye,updateTemplateHash as ve,getStateFilePath as Me,stubCallerIdentity as Fe}from"./types/index.js";import{CloudFormationEventMonitor as be}from"./aws/index.js";import{CdkService as he,CdkArgumentBuilder as Ke,CdkProcessManager as Ge,CdkEventMonitor as Ve,startStackMonitoring as He,DEFAULT_DEPLOY_TIMEOUT_MS as Xe,isCdkError as we,formatInfrastructureError as je,getStructuralHint as Qe,getSourceContext as qe,hasCdkDifferences as ze,parseDiffOutput as Je,CloudFormationService as We,CloudFormationError as Ze,EcsService as $e,EcsError as er,EcsServiceResolver as rr,TemplateHashService as tr,TemplateHashError as or,CdkContextBuilder as ir,emitProgress as ar,PROGRESS_MESSAGES as sr,parseBuildPhase as nr,buildStepContextBuildConfig as cr,convertCloudFormationOutputsToRecord as Er,ApplicationStackService as Sr}from"./services/index.js";import{CdkError as Ar}from"./types/errors/index.js";import{BaseServiceError as pr,ValidationError as ur,AuthError as _r,AwsError as Or,DeploymentError as Rr,NetworkError as mr,FileSystemError as dr,ConfigError as Pr,toServiceError as Cr}from"./types/errors/index.js";import{filterDangerousEnvVars as fr,maskSensitiveOutput as gr,parseShellArgs as xr,sleep as Dr}from"@fjall/util";import{hasDockerfile as Lr}from"./util/dockerfileDetection.js";import{createSequencedCallbacks as kr}from"./util/sequencedCallbacks.js";import{fileExists as vr}from"@fjall/util/fsHelpers";import{success as Fr,failure as Yr,isSuccess as br,isFailure as Br}from"@fjall/generator";import{deploy as Kr}from"./orchestration/index.js";import{destroy as Vr}from"./orchestration/index.js";import{partitionAccounts as Xr}from"./orchestration/index.js";import{buildRegionList as jr,buildAccountRegionPairs as Qr,cascadeHomeRegion as qr,cascadeOperationKey as zr}from"./orchestration/index.js";import{projectScalarSummary as Wr,projectAccountRows as Zr}from"./orchestration/index.js";import{reconcileProviderAccounts as et,mergeReconciledProviderAccounts as rt}from"./orchestration/index.js";import{decideNextTransition as ot,reconcileTrailMigration as it,decommissionMemberTrailStorage as at,ORG_TRAIL_BUCKET_OUTPUT_KEY as st,TRAIL_BUCKET_OUTPUT_KEY as nt,TRAIL_KEY_ARN_OUTPUT_KEY as ct}from"./orchestration/index.js";import{unlockBucket as St,unlockQueue as lt}from"./orchestration/index.js";import{triageBucketPolicy as Tt,isEnforceSslStatement as pt,toResourcePolicyStatements as ut,restoreBucketPolicy as _t,synthesiseEnforceSslDocument as Ot,ensureEnforceSsl as Rt,restoreAndReconcileQuarantinedBucket as mt}from"./orchestration/index.js";import{verifyOrgTrailDelivery as Pt}from"./aws/cloudtrail/orgTrailDelivery.js";import{assumeRootForTask as Nt,isRootTaskPolicyArn as ft,ROOT_TASK_POLICY_ARNS as gt,MAX_ROOT_SESSION_SECONDS as xt}from"./aws/sts/assumeRoot.js";import{parseAccountsConfiguration as It,flattenAccountsToEnvironments as Lt,extractAllAccountNames as Ut,accountsConfigToOUTree as kt,isStringArray as yt,isAccountsConfig as vt,isOuOnlyAccountBucket as Mt,OU_ONLY_ACCOUNT_BUCKETS as Ft}from"./orchestration/index.js";import{runOpenNextBuild as bt}from"./orchestration/index.js";import{runOrganisationSetup as ht,ORG_SETUP_PHASES as Kt}from"./orchestration/index.js";import{FrameworkRegistry as Vt}from"./orchestration/index.js";import{openNextBuilder as Xt,dockerBuilder as wt}from"./orchestration/index.js";import{StepRegistry as Qt,getDestroyStepId as qt}from"./steps/index.js";export{W as APPLICATION_DEPLOY_ORDER,Z as APPLICATION_DESTROY_ORDER,z as APPLICATION_STACKS,xe as ApplicationError,Sr as ApplicationStackService,_r as AuthError,Or as AwsError,pr as BaseServiceError,E as CASCADE_ACCOUNT_STATUSES,c as CASCADE_PHASES,fe as CDK_NO_STACKS_MATCH,Ke as CdkArgumentBuilder,ir as CdkContextBuilder,Ar as CdkError,Ve as CdkEventMonitor,Ge as CdkProcessManager,he as CdkService,Ze as CloudFormationError,be as CloudFormationEventMonitor,We as CloudFormationService,Pr as ConfigError,Xe as DEFAULT_DEPLOY_TIMEOUT_MS,s as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,i as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,a as DEPLOYMENT_EVENT_STATUS_REASON_MAX,n as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,o as DEPLOYMENT_EVENT_TYPES,Rr as DeploymentError,t as DeploymentEventSchema,er as EcsError,$e as EcsService,rr as EcsServiceResolver,dr as FileSystemError,Ie as FjallStateFileSchema,Vt as FrameworkRegistry,ge as INFRASTRUCTURE_FILENAME,w as INFRASTRUCTURE_STEP_NAMES,j as INFRA_STEP_NAME,xt as MAX_ROOT_SESSION_SECONDS,mr as NetworkError,$ as OPENNEXT_DEPLOY_ORDER,ee as OPENNEXT_DESTROY_ORDER,oe as OPENNEXT_PARALLEL_GROUPS,de as OPENNEXT_PATTERNS,J as ORGANISATION_TYPES,Kt as ORG_SETUP_PHASES,st as ORG_TRAIL_BUCKET_OUTPUT_KEY,Ft as OU_ONLY_ACCOUNT_BUCKETS,re as PARALLEL_DEPLOY_GROUPS,te as PARALLEL_DESTROY_GROUPS,ie as PARALLEL_OPERATION_TYPES,sr as PROGRESS_MESSAGES,q as ProgressReporter,K as ROOT_ACCESS_FEATURES,gt as ROOT_TASK_POLICY_ARNS,G as RootAccessEnablementError,B as SECURITY_SERVICE_PRINCIPALS,C as SERVICE_PRINCIPALS,Ne as STACK_FAILED_STATE_PATTERN,Ce as STACK_NOT_FOUND_PATTERN,H as STEP_IDS,X as STEP_NAMES,l as SimpleAwsProvider,Qt as StepRegistry,nt as TRAIL_BUCKET_OUTPUT_KEY,ct as TRAIL_KEY_ARN_OUTPUT_KEY,or as TemplateHashError,tr as TemplateHashService,ur as ValidationError,kt as accountsConfigToOUTree,v as activateCostAllocationTags,f as activateTrustedAccess,Nt as assumeRootForTask,Qr as buildAccountRegionPairs,y as buildAccountToOUMap,jr as buildRegionList,cr as buildStepContextBuildConfig,_ as buildTargetSetUnreadyAdvisory,u as buildTargetUnreadyAdvisory,qr as cascadeHomeRegion,zr as cascadeOperationKey,M as checkIdentityCentreStatus,T as checkTargetReadiness,Er as convertCloudFormationOutputsToRecord,L as createAccount,ke as createEmptyState,kr as createSequencedCallbacks,ot as decideNextTransition,at as decommissionMemberTrailStorage,ye as deleteStateFile,Kr as deploy,Pe as deriveResourcesFromManifestStacks,m as describeOrganisation,p as describeTargetReadinessReason,Vr as destroy,wt as dockerBuilder,ar as emitProgress,h as enableCentralisedRootAccess,g as enableIpamDelegatedAdmin,d as enablePolicyTypes,N as enableRamSharing,P as enableServiceAccess,Rt as ensureEnforceSsl,R as ensureOrganisationExists,U as ensureOrganisationalUnitsExist,Ut as extractAllAccountNames,F as extractErrorName,Yr as failure,vr as fileExists,fr as filterDangerousEnvVars,I as findAccount,Lt as flattenAccountsToEnvironments,je as formatInfrastructureError,Ee as getApplicationDeployOrder,Se as getApplicationDestroyOrder,le as getApplicationStackName,Oe as getApplicationStepId,_e as getApplicationStepName,qt as getDestroyStepId,Ae as getOrganisationStackName,ne as getParallelDeployGroups,ce as getParallelDestroyGroups,qe as getSourceContext,Me as getStateFilePath,Qe as getStructuralHint,ze as hasCdkDifferences,Lr as hasDockerfile,vt as isAccountsConfig,ae as isApplicationOperation,Te as isApplicationStack,we as isCdkError,pt as isEnforceSslStatement,Br as isFailure,Y as isOULeaf,me as isOpenNextPattern,se as isOrganisationOperation,Mt as isOuOnlyAccountBucket,pe as isQuarantineDetail,ue as isRetainedBucketsDetail,ft as isRootTaskPolicyArn,yt as isStringArray,br as isSuccess,D as listAccounts,gr as maskSensitiveOutput,rt as mergeReconciledProviderAccounts,Xt as openNextBuilder,It as parseAccountsConfiguration,nr as parseBuildPhase,Je as parseDiffOutput,xr as parseShellArgs,Xr as partitionAccounts,k as placeAccountsInOUs,Zr as projectAccountRows,Wr as projectScalarSummary,Le as readStateFile,et as reconcileProviderAccounts,it as reconcileTrailMigration,b as registerSecurityDelegates,mt as restoreAndReconcileQuarantinedBucket,_t as restoreBucketPolicy,bt as runOpenNextBuild,ht as runOrganisationSetup,Dr as sleep,He as startStackMonitoring,Fe as stubCallerIdentity,Fr as success,Ot as synthesiseEnforceSslDocument,Re as toPascalCase,ut as toResourcePolicyStatements,Cr as toServiceError,Tt as triageBucketPolicy,St as unlockBucket,lt as unlockQueue,x as updateBackupGlobalSettings,ve as updateTemplateHash,Pt as verifyOrgTrailDelivery,De as wrapApplicationError,Ue as writeStateFile};
@@ -1 +1 @@
1
- import{join as $}from"path";import{CloudFormationClient as q}from"@aws-sdk/client-cloudformation";import{EC2Client as z}from"@aws-sdk/client-ec2";import{success as U,failure as m}from"@fjall/generator";import{logger as E}from"@fjall/util/logger";import{maskSensitiveOutput as S,toPascalCase as Y}from"@fjall/util";import{parseDockerServicesFromManifest as J}from"@fjall/util/manifest";import{stubCallerIdentity as Q}from"../types/deployment/index.js";import{getApplicationDeployOrder as V,getApplicationStackName as X,getApplicationStepName as F,getApplicationStepId as H,APPLICATION_STACKS as j}from"../types/operations.js";import{CdkContextBuilder as Z}from"../services/supporting/CdkContextBuilder.js";import{buildParamsContext as ee,bootstrapOrFail as te}from"./contextHelpers.js";import{runDetectionPipeline as oe}from"./detectionPipeline.js";import{STEP_IDS as O,STEP_NAMES as re}from"../types/stepDefinitions.js";import{StepRegistry as ne}from"../steps/stepRegistry.js";import{checkTargetReadiness as ae}from"../aws/targetReadiness.js";import{cascadeHomeRegion as ie}from"./cascadeHelpers.js";import{withStepLifecycle as se}from"./stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as G,runDockerBuild as ce}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as de,deployParallelPhase as le,deployStackSequential as pe,runDockerPreCompute as ue,createBuildCallbacks as fe}from"./applicationDeployHelpers.js";import{deployCodeOnly as ge}from"./codeOnlyDeploy.js";const R="applicationDeploy";function me(i){const a=Object.entries(i);if(a.length===0)return;const r={};for(const[t,s]of a){const b=`${Y(t)}ImageTag`;r[b]=s}return r}async function Me(i,a,r){const{callbacks:t,options:s}=i,b=Date.now(),D=Z.buildDeploymentContext({deployType:"application",target:r.appName,path:r.path,region:a.awsProvider.getRegion(),callerIdentity:Q(a.awsProvider.getAccountId()),...ee({orgConfig:i.orgConfig,identity:i.identity,skipOidc:i.options?.skipOidc})},{verbose:s?.verbose,infraOnly:s?.infraOnly},i.orgConfig),p=a.frameworkRegistry.resolve({appPath:r.path});let w;const k=!!s?.imageTag;if(p&&!k){w=p.builder.plan({appPath:r.path},p.detection);const e=fe(t),o=await p.builder.build(r.path,w,e,{skipBuild:s?.skipBuild,infraOnly:s?.infraOnly});if(!o.success){const n=new Error(S(o.error.message));return t.onError?.(n),m(n)}}if(s?.deployOnly||k){t.onLog?.(k?`Rollback mode \u2014 rolling to image tag ${s?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const e=$(r.path,"cdk.out"),o=J(e),n=o.length>0,l=p?.detection.hasDockerfile===!0,d=n||l;E.debug(R,"Deploy-only branch entered",{isRollback:k,imageTag:s?.imageTag,appName:r.appName,appPath:r.path,cdkOutPath:e,dockerProviderAvailable:i.dockerProvider!==void 0,builderName:p?.builder.name,hasDockerfileFromManifest:n,hasDockerfileFromDisk:l,hasDockerfile:d,manifestDockerServiceCount:o.length,manifestDockerPaths:o.map(u=>u.docker.path)}),!n&&!l&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");let f={};if(!k&&i.dockerProvider!==void 0&&d){E.debug(R,"Running Docker build before code-only deploy",{source:n?"manifest":"disk"});const u=await ce(i,a,r,t);if(!u.success)return m(u.error);f=u.data}else E.debug(R,"Skipping Docker build",{reason:k?"rollback":i.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return ge(i,a,r,f)}t.onLog?.("Analysing infrastructure\u2026","info");const T=await oe(r,a,D,t);if(!T.success){const e=new Error(S(T.error.message));return t.onError?.(e),m(e)}const c=T.data;try{await t.onDetectionComplete?.({...c,builderName:p?.builder.name??"unknown"})}catch(e){const o=e instanceof Error?e.message:String(e),n=new Error(S(o));return t.onError?.(n),m(n)}const K={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:s?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...p&&{builderName:p.builder.name}},N=ne.getSteps(K),A=N.findIndex(e=>e.id===O.TARGET_READINESS),I=await se(t,{stepId:O.TARGET_READINESS,stepName:re.TARGET_READINESS,...A>=0&&{stepIndex:A,totalSteps:N.length}},async()=>{if(s?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const e=a.awsProvider.getAccountId(),o=i.orgConfig?.providerAccounts.find(f=>f.id===e)?.name,n=a.awsProvider.getCredentials(),l=ie(i.orgConfig),d=await ae({cloudFormation:a.awsProvider.getClient(q),ec2:new z({region:l,...n!==void 0&&{credentials:n}})},{id:e,...o!==void 0&&{name:o}},a.awsProvider.getRegion(),i.orgConfig,i.abortSignal);return d.success?d.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(S(d.data.advisory))}:{kind:"error",error:new Error(S(d.error.message))}});if(!I.success)return I;const g=w?w.deployOrder:V({pattern:c.pattern,resources:c.resources}),h=g.length;if(!c.hasDifferences&&!s?.force){t.onLog?.("No infrastructure changes detected","info");const e=c.hasDockerfile&&i.dockerProvider!==void 0&&g.includes(j.COMPUTE);for(let n=0;n<g.length;n++){const l=g[n];e&&l===j.COMPUTE&&(t.onStepStart?.(O.DOCKER_OPERATIONS,G),t.onStepComplete?.(O.DOCKER_OPERATIONS,G,"skipped"));const d=H(l,"deploy"),f=F(l,"deploy");t.onStepStart?.(d,f,n,h),t.onStepComplete?.(d,f,"skipped",n,h)}const o=await a.stackService.resolveWebsiteUrl(r.appName);return U({target:r.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,durationMs:Date.now()-b})}const v=await te(a,D,t);if(!v.success)return v;const C={},P=new Map;for(let e=0;e<g.length;e++){const o=g[e],n=X(r.appName,o),l=H(o,"deploy"),d=F(o,"deploy");if(!(c.stackChanges.get(n)??!0)&&!s?.force){t.onStepStart?.(l,d,e,h),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(l,d,"skipped",e,h);continue}const u=de(g,e,c.stackChanges,r.appName,s?.force);if(u.length>=2){const B=await le(u,r,a,D,t,e,h,c,C,P);if(!B.success)return m(B.error);e+=u.length-1;continue}const y=await ue(o,i,a,r,t,c.hasDockerfile);if(y!==null&&!y.success)return m(y.error);const W=y!==null&&y.success?y.data.contentHashTagsByService:{},L=me(W),x=await pe(o,a,D,t,e,h,C,L!==void 0?{parameters:L}:void 0);if(!x.success)return m(x.error);const M=c.currentHashes.get(n);M&&P.set(n,M)}if(P.size>0){const e=await a.hashService.updateStateAfterDeploy(r.path,P);if(!e.success){const o=S(e.error.message);E.debug(R,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const _=await a.stackService.resolveWebsiteUrl(r.appName);return _&&(C.websiteUrl=_),U({target:r.appName,deploymentType:"application",outputs:Object.keys(C).length>0?C:void 0,durationMs:Date.now()-b})}export{Me as deployApplication};
1
+ import{join as $}from"path";import{CloudFormationClient as q}from"@aws-sdk/client-cloudformation";import{EC2Client as z}from"@aws-sdk/client-ec2";import{success as U,failure as g}from"@fjall/generator";import{logger as E}from"@fjall/util/logger";import{imageTagParameterName as Y,maskSensitiveOutput as S}from"@fjall/util";import{parseDockerServicesFromManifest as J}from"@fjall/util/manifest";import{stubCallerIdentity as Q}from"../types/deployment/index.js";import{getApplicationDeployOrder as V,getApplicationStackName as X,getApplicationStepName as F,getApplicationStepId as H,APPLICATION_STACKS as j}from"../types/operations.js";import{CdkContextBuilder as Z}from"../services/supporting/CdkContextBuilder.js";import{buildParamsContext as ee,bootstrapOrFail as te}from"./contextHelpers.js";import{runDetectionPipeline as oe}from"./detectionPipeline.js";import{STEP_IDS as O,STEP_NAMES as re}from"../types/stepDefinitions.js";import{StepRegistry as ne}from"../steps/stepRegistry.js";import{checkTargetReadiness as ae}from"../aws/targetReadiness.js";import{cascadeHomeRegion as ie}from"./cascadeHelpers.js";import{withStepLifecycle as se}from"./stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as G,runDockerBuild as ce}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as de,deployParallelPhase as le,deployStackSequential as pe,runDockerPreCompute as ue,createBuildCallbacks as fe}from"./applicationDeployHelpers.js";import{deployCodeOnly as me}from"./codeOnlyDeploy.js";const R="applicationDeploy";function ge(i){const a=Object.entries(i);if(a.length===0)return;const r={};for(const[t,s]of a){const C=Y(t);r[C]=s}return r}async function Me(i,a,r){const{callbacks:t,options:s}=i,C=Date.now(),D=Z.buildDeploymentContext({deployType:"application",target:r.appName,path:r.path,region:a.awsProvider.getRegion(),callerIdentity:Q(a.awsProvider.getAccountId()),...ee({orgConfig:i.orgConfig,identity:i.identity,skipOidc:i.options?.skipOidc})},{verbose:s?.verbose,infraOnly:s?.infraOnly},i.orgConfig),p=a.frameworkRegistry.resolve({appPath:r.path});let w;const k=!!s?.imageTag;if(p&&!k){w=p.builder.plan({appPath:r.path},p.detection);const e=fe(t),o=await p.builder.build(r.path,w,e,{skipBuild:s?.skipBuild,infraOnly:s?.infraOnly});if(!o.success){const n=new Error(S(o.error.message));return t.onError?.(n),g(n)}}if(s?.deployOnly||k){t.onLog?.(k?`Rollback mode \u2014 rolling to image tag ${s?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const e=$(r.path,"cdk.out"),o=J(e),n=o.length>0,l=p?.detection.hasDockerfile===!0,d=n||l;E.debug(R,"Deploy-only branch entered",{isRollback:k,imageTag:s?.imageTag,appName:r.appName,appPath:r.path,cdkOutPath:e,dockerProviderAvailable:i.dockerProvider!==void 0,builderName:p?.builder.name,hasDockerfileFromManifest:n,hasDockerfileFromDisk:l,hasDockerfile:d,manifestDockerServiceCount:o.length,manifestDockerPaths:o.map(u=>u.docker.path)}),!n&&!l&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");let f={};if(!k&&i.dockerProvider!==void 0&&d){E.debug(R,"Running Docker build before code-only deploy",{source:n?"manifest":"disk"});const u=await ce(i,a,r,t);if(!u.success)return g(u.error);f=u.data}else E.debug(R,"Skipping Docker build",{reason:k?"rollback":i.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return me(i,a,r,f)}t.onLog?.("Analysing infrastructure\u2026","info");const N=await oe(r,a,D,t);if(!N.success){const e=new Error(S(N.error.message));return t.onError?.(e),g(e)}const c=N.data;try{await t.onDetectionComplete?.({...c,builderName:p?.builder.name??"unknown"})}catch(e){const o=e instanceof Error?e.message:String(e),n=new Error(S(o));return t.onError?.(n),g(n)}const K={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:s?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...p&&{builderName:p.builder.name}},T=ne.getSteps(K),A=T.findIndex(e=>e.id===O.TARGET_READINESS),I=await se(t,{stepId:O.TARGET_READINESS,stepName:re.TARGET_READINESS,...A>=0&&{stepIndex:A,totalSteps:T.length}},async()=>{if(s?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const e=a.awsProvider.getAccountId(),o=i.orgConfig?.providerAccounts.find(f=>f.id===e)?.name,n=a.awsProvider.getCredentials(),l=ie(i.orgConfig),d=await ae({cloudFormation:a.awsProvider.getClient(q),ec2:new z({region:l,...n!==void 0&&{credentials:n}})},{id:e,...o!==void 0&&{name:o}},a.awsProvider.getRegion(),i.orgConfig,i.abortSignal);return d.success?d.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(S(d.data.advisory))}:{kind:"error",error:new Error(S(d.error.message))}});if(!I.success)return I;const m=w?w.deployOrder:V({pattern:c.pattern,resources:c.resources}),h=m.length;if(!c.hasDifferences&&!s?.force){t.onLog?.("No infrastructure changes detected","info");const e=c.hasDockerfile&&i.dockerProvider!==void 0&&m.includes(j.COMPUTE);for(let n=0;n<m.length;n++){const l=m[n];e&&l===j.COMPUTE&&(t.onStepStart?.(O.DOCKER_OPERATIONS,G),t.onStepComplete?.(O.DOCKER_OPERATIONS,G,"skipped"));const d=H(l,"deploy"),f=F(l,"deploy");t.onStepStart?.(d,f,n,h),t.onStepComplete?.(d,f,"skipped",n,h)}const o=await a.stackService.resolveWebsiteUrl(r.appName);return U({target:r.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,durationMs:Date.now()-C})}const v=await te(a,D,t);if(!v.success)return v;const b={},P=new Map;for(let e=0;e<m.length;e++){const o=m[e],n=X(r.appName,o),l=H(o,"deploy"),d=F(o,"deploy");if(!(c.stackChanges.get(n)??!0)&&!s?.force){t.onStepStart?.(l,d,e,h),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(l,d,"skipped",e,h);continue}const u=de(m,e,c.stackChanges,r.appName,s?.force);if(u.length>=2){const B=await le(u,r,a,D,t,e,h,c,b,P);if(!B.success)return g(B.error);e+=u.length-1;continue}const y=await ue(o,i,a,r,t,c.hasDockerfile);if(y!==null&&!y.success)return g(y.error);const W=y!==null&&y.success?y.data.contentHashTagsByService:{},L=ge(W),x=await pe(o,a,D,t,e,h,b,L!==void 0?{parameters:L}:void 0);if(!x.success)return g(x.error);const M=c.currentHashes.get(n);M&&P.set(n,M)}if(P.size>0){const e=await a.hashService.updateStateAfterDeploy(r.path,P);if(!e.success){const o=S(e.error.message);E.debug(R,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const _=await a.stackService.resolveWebsiteUrl(r.appName);return _&&(b.websiteUrl=_),U({target:r.appName,deploymentType:"application",outputs:Object.keys(b).length>0?b:void 0,durationMs:Date.now()-C})}export{Me as deployApplication};
@@ -1 +1 @@
1
- import{join as I}from"path";import{logger as f}from"@fjall/util/logger";import{maskSensitiveOutput as i,getErrorMessage as v}from"@fjall/util";import{stubCallerIdentity as b}from"../types/deployment/index.js";import{CloudFormationClient as P,DescribeStacksCommand as _}from"@aws-sdk/client-cloudformation";import{S3Client as g}from"@aws-sdk/client-s3";import{BackupClient as x}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as F,describeSurvivingBackupVault as M,formatSurvivingVaultWarning as K}from"../aws/organisations/backup.js";import{composeSdkAbortSignal as L}from"../aws/organisations/types.js";import{ORGANISATION_TYPES as R,getOrganisationStackName as V}from"../types/operations.js";import{CdkContextBuilder as j}from"../services/supporting/CdkContextBuilder.js";import{regionSuffix as U}from"../types/FjallState.js";import{buildParamsContext as G,assumeCascadeRole as H,forwardOutput as E}from"./contextHelpers.js";import{cascadeOperationKey as W}from"./cascadeHelpers.js";import{capturedSweepBuckets as Y,cleanupFailedStack as q,preEmptyStackBuckets as z,sweepOrphanedDestroyBuckets as N}from"./stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as J,STACK_FAILED_STATE_PATTERN as Q}from"../types/constants.js";async function ye(a,o,c,e,n,t,r,T){const y=Date.now(),u=W(e.name,t),l=(s,p=s)=>(r.onCascadeAccountComplete?.(u,!1,p,t),{accountName:e.name,accountId:e.id,region:t,success:!1,duration:Date.now()-y,error:s});r.onCascadeAccountStart?.(u,e.id,t,n);const S=await H(o.awsProvider,e.id,t,`fjall-cascade-destroy-${e.name}`);if(!S.success){const s=i(S.error.message);return l(`AssumeRole failed: ${s}`,s)}const{provider:d,credentials:w}=S.data,O=I(c.path,`cdk.out.${e.id}.${U(t)}`),k=j.buildDeploymentContext({deployType:n,target:c.target,path:c.path,assemblyDir:O,region:t,accountName:e.name,callerIdentity:b(e.id),...G({orgConfig:a.orgConfig,identity:a.identity,region:t,primaryRegion:T?.primaryRegion})},{verbose:a.options?.verbose},a.orgConfig);r.onCascadeAccountPhaseChange?.(u,"synth",t);const A=await o.cdkService.runCdkSynth(k,E(r));if(!A.success)return l(i(`Synth failed: ${A.error}`));r.onCascadeAccountPhaseChange?.(u,"destroy",t);const m=V(n==="platform"?R.PLATFORM:R.ACCOUNT);F(e.environment,a.orgConfig?.disasterRecoveryRegion)&&await X(d.getClient(x),t,r);const $=await z(d.getClient(P),d.getClient(g),m,r,a.abortSignal),C=Y($),D=await o.cdkService.runCdkDestroy(k,m,E(r),s=>r.onCascadeAccountResourceProgress?.(u,s,t),d,!0,w);if(!D.success){const s=D.error;if(s.includes(Q)){f.warn("cascadeDestroy",`CDK destroy failed on ${m} in failed state, retrying via CloudFormation API`,{region:t,account:e.name});try{await q(m,t,w,{accountId:e.id,abortSignal:a.abortSignal},r)}catch(B){const h=`cleanupFailedStack threw for ${m}: ${i(v(B))}`;f.warn("cascadeDestroy",h),r.onLog?.(h,"warn")}const p=await Z(m,d.getClient(P),a.abortSignal);return p.deleted?(C.length>0&&await N(d.getClient(g),C,r,a.abortSignal),r.onCascadeAccountComplete?.(u,!0,void 0,t),{accountName:e.name,accountId:e.id,region:t,success:!0,duration:Date.now()-y}):p.error?l(i(p.error)):l(i(`Stack ${m} cleanup attempted but stack still exists in ${t}`))}return l(i(s))}return C.length>0&&await N(d.getClient(g),C,r,a.abortSignal),r.onCascadeAccountComplete?.(u,!0,void 0,t),{accountName:e.name,accountId:e.id,region:t,success:!0,duration:Date.now()-y}}async function X(a,o,c){try{const e=await M(a,o);if(!e.success){f.debug("cascadeDestroy","Backup-vault survival probe failed",{region:o,error:i(e.error.message)});return}if(e.data===null)return;c.onProgress?.({type:"warning",message:K(e.data),metadata:{source:"backup-vault-survival"}}),f.warn("cascadeDestroy","Backup vault survives destroy",{region:o,vaultName:e.data.vaultName,recoveryPointCount:e.data.recoveryPointCount,lockPermanent:e.data.lockPermanent})}catch(e){f.debug("cascadeDestroy","Backup-vault survival probe threw",{region:o,error:i(v(e))})}}async function Z(a,o,c){try{const n=(await o.send(new _({StackName:a}),{abortSignal:L(c)})).Stacks?.[0]?.StackStatus;return!n||n==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${n} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(J))return{deleted:!0};const n=i(v(e));return f.debug("cascadeDestroy","Stack verification failed",{error:n}),{deleted:!1,error:`Stack verification failed: ${n}`}}}export{ye as destroyCascadeAccount};
1
+ import{join as B}from"path";import{logger as f}from"@fjall/util/logger";import{maskSensitiveOutput as i,getErrorMessage as g}from"@fjall/util";import{stubCallerIdentity as I}from"../types/deployment/index.js";import{CloudFormationClient as P,DescribeStacksCommand as _}from"@aws-sdk/client-cloudformation";import{S3Client as v}from"@aws-sdk/client-s3";import{BackupClient as x}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as F,describeSurvivingBackupVault as M,formatSurvivingVaultWarning as K}from"../aws/organisations/backup.js";import{composeSdkAbortSignal as L}from"../aws/organisations/types.js";import{ORGANISATION_TYPES as R,getOrganisationStackName as V}from"../types/operations.js";import{CdkContextBuilder as j}from"../services/supporting/CdkContextBuilder.js";import{regionSuffix as U}from"../types/FjallState.js";import{buildParamsContext as G,assumeCascadeRole as H,forwardOutput as E}from"./contextHelpers.js";import{cascadeOperationKey as W}from"./cascadeHelpers.js";import{capturedSweepBuckets as Y,cleanupFailedStack as q,preEmptyStackBuckets as z,sweepOrphanedDestroyBuckets as N}from"./stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as J,STACK_FAILED_STATE_PATTERN as Q}from"../types/constants.js";async function Se(a,n,c,e,t,r,o,T){const S=Date.now(),u=W(e.name,r),l=(s,p=s)=>(o.onCascadeAccountComplete?.(u,!1,p,r),{accountName:e.name,accountId:e.id,region:r,success:!1,duration:Date.now()-S,error:s});o.onCascadeAccountStart?.(u,e.id,r,t);const y=await H(n.awsProvider,e.id,r,`fjall-cascade-destroy-${e.name}`);if(!y.success){const s=i(y.error.message);return l(`AssumeRole failed: ${s}`,s)}const{provider:d,credentials:w}=y.data,O=B(c.path,`cdk.out.${e.id}.${U(r)}`),k=j.buildDeploymentContext({deployType:t,target:c.target,path:c.path,assemblyDir:O,region:r,accountName:e.name,callerIdentity:I(e.id),...G({orgConfig:a.orgConfig,identity:a.identity,region:r,primaryRegion:T?.primaryRegion})},{verbose:a.options?.verbose},a.orgConfig);o.onCascadeAccountPhaseChange?.(u,"synth",r);const A=await n.cdkService.runCdkSynth(k,E(o));if(!A.success)return l(i(`Synth failed: ${A.error}`));o.onCascadeAccountPhaseChange?.(u,"destroy",r);const m=V(t==="platform"?R.PLATFORM:R.ACCOUNT);F(e.environment,a.orgConfig?.disasterRecoveryRegion)&&await X(d.getClient(x),r,o,a.abortSignal);const $=await z(d.getClient(P),d.getClient(v),m,o,a.abortSignal),C=Y($),D=await n.cdkService.runCdkDestroy(k,m,E(o),s=>o.onCascadeAccountResourceProgress?.(u,s,r),d,!0,w);if(!D.success){const s=D.error;if(s.includes(Q)){f.warn("cascadeDestroy",`CDK destroy failed on ${m} in failed state, retrying via CloudFormation API`,{region:r,account:e.name});try{await q(m,r,w,{accountId:e.id,abortSignal:a.abortSignal},o)}catch(b){const h=`cleanupFailedStack threw for ${m}: ${i(g(b))}`;f.warn("cascadeDestroy",h),o.onLog?.(h,"warn")}const p=await Z(m,d.getClient(P),a.abortSignal);return p.deleted?(C.length>0&&await N(d.getClient(v),C,o,a.abortSignal),o.onCascadeAccountComplete?.(u,!0,void 0,r),{accountName:e.name,accountId:e.id,region:r,success:!0,duration:Date.now()-S}):p.error?l(i(p.error)):l(i(`Stack ${m} cleanup attempted but stack still exists in ${r}`))}return l(i(s))}return C.length>0&&await N(d.getClient(v),C,o,a.abortSignal),o.onCascadeAccountComplete?.(u,!0,void 0,r),{accountName:e.name,accountId:e.id,region:r,success:!0,duration:Date.now()-S}}async function X(a,n,c,e){try{const t=await M(a,n,e);if(!t.success){f.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:i(t.error.message)});return}if(t.data===null)return;c.onProgress?.({type:"warning",message:K(t.data),metadata:{source:"backup-vault-survival"}}),f.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:t.data.vaultName,recoveryPointCount:t.data.recoveryPointCount,lockPermanent:t.data.lockPermanent})}catch(t){f.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:i(g(t))})}}async function Z(a,n,c){try{const t=(await n.send(new _({StackName:a}),{abortSignal:L(c)})).Stacks?.[0]?.StackStatus;return!t||t==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${t} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(J))return{deleted:!0};const t=i(g(e));return f.debug("cascadeDestroy","Stack verification failed",{error:t}),{deleted:!1,error:`Stack verification failed: ${t}`}}}export{Se as destroyCascadeAccount};
@@ -1 +1 @@
1
- import{join as L}from"path";import{success as B,failure as h}from"@fjall/generator";import{logger as y}from"@fjall/util/logger";import{maskSensitiveOutput as p,mapSettledWithConcurrency as U}from"@fjall/util";import{ORGANISATION_TYPES as R,getOrganisationStackName as j}from"../types/operations.js";import{CdkContextBuilder as _}from"../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as K}from"../types/deployment/index.js";import{CloudFormationService as H}from"../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as z,regionSuffix as G}from"../types/FjallState.js";import{BackupClient as Y}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as W,describeBackupVaultExists as X}from"../aws/organisations/backup.js";import{buildParamsContext as q,collectStackOutputs as M,assumeCascadeRole as v,forwardOutput as x}from"./contextHelpers.js";import{accountTier as T}from"@fjall/util";import{DEFAULT_REGION as V}from"../aws/utils/regions.js";const J=4;function le(o){const n=o.find(e=>T(e)==="platform"),c=o.filter(e=>T(e)==="account");return{platformAccount:n,memberAccounts:c}}function Q(o){const n=o?.primaryRegion??V,c=o?.secondaryRegions??[],e=new Set([n,...c]),i=o?.disasterRecoveryRegion;return i&&e.add(i),[...e]}function ge(o){return Q(o)[0]??V}function Z(o,n){return`${o} (${n})`}function Ce(o,n){const c=[];for(const e of n)for(const i of o)c.push({account:i,region:e});return c}import{buildCascadeRoleArn as Se}from"./contextHelpers.js";async function he(o,n,c,e){c.onLog?.(`Verifying cascade role access for ${n.length} account(s)\u2026`,"info");const i=await U(n,J,async a=>v(o.awsProvider,a.id,a.region??o.awsProvider.getRegion(),`fjall-preflight-${a.name}`,e)),s=[];return i.forEach((a,t)=>{const r=n[t];if(r){if(a.status==="rejected"){s.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||s.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),s.length===0&&c.onLog?.(`Cascade role access verified for ${n.length} account(s)`,"info"),s}async function ye(o,n,c,e,i,s,a){const t=a?.region??e.region??n.awsProvider.getRegion(),r=Z(e.name,t),u=a?.orgConfig??o.orgConfig,l=a?.ipamPoolId;s.onCascadeAccountStart?.(r,e.id,t,i);const d=await v(n.awsProvider,e.id,t,`fjall-cascade-${e.name}`,o.abortSignal);if(!d.success)return s.onCascadeAccountComplete?.(r,!1,p(d.error.message),t),h(new Error(`Failed to assume role for ${e.name}: ${p(d.error.message)}`));const{provider:g,credentials:C}=d.data,P=L(o.workingDirectory,"fjall",i==="platform"?R.PLATFORM:R.ACCOUNT),D=L(P,`cdk.out.${e.id}.${G(t)}`),w=_.buildDeploymentContext({deployType:i,target:c.target,path:P,assemblyDir:D,environment:e.environment??void 0,region:t,accountName:e.name,callerIdentity:K(e.id),ipamPoolId:l,...q({orgConfig:u,identity:o.identity,skipOidc:o.options?.skipOidc,region:t,primaryRegion:a?.primaryRegion,trailLifecycle:e.trailLifecycle})},{verbose:o.options?.verbose},u);if(W(e.environment,u?.disasterRecoveryRegion)){const f=await X(g.getClient(Y));if(!f.success)return s.onCascadeAccountComplete?.(r,!1,p(f.error.message),t),h(new Error(`Backup vault probe failed for ${e.name}: ${p(f.error.message)}`));w.fjallAdoptBackupVault=f.data}s.onCascadeAccountPhaseChange?.(r,"synth",t);const $=await n.cdkService.runCdkSynth(w,x(s),C);if(!$.success)return s.onCascadeAccountComplete?.(r,!1,p(`Synth failed: ${$.error}`),t),h(new Error(`Synth failed for ${e.name}: ${p($.error)}`));const m=j(i==="platform"?R.PLATFORM:R.ACCOUNT),k=z(P,e.id,t),S=new H(g),{changed:b,currentHash:I}=await n.hashService.compareCascadeStack(D,m,k);if(!b&&o.options?.force!==!0&&await S.stackExists(m)){const f=await S.getStackOutputs(m);f.success||y.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:m,account:e.name});const F=M(f);return s.onLog?.(`${e.name}: no infrastructure changes \u2014 skipping deploy`,"info"),s.onCascadeAccountComplete?.(r,!0,void 0,t,F,!0),B({outputs:F,skipped:!0})}s.onCascadeAccountPhaseChange?.(r,"bootstrap",t);const A=await n.cdkService.runCdkBootstrap(w,x(s),C);if(!A.success)return s.onCascadeAccountComplete?.(r,!1,p(`Bootstrap failed: ${A.error}`),t),h(new Error(`Bootstrap failed for ${e.name}: ${p(A.error)}`));s.onCascadeAccountPhaseChange?.(r,"deploy",t);const O=await n.cdkService.runCdkDeploy(w,m,x(s),f=>s.onCascadeAccountResourceProgress?.(r,f,t),g,C);if(!O.success)return s.onCascadeAccountComplete?.(r,!1,p(O.error),t),h(new Error(p(O.error)));const E=await S.getStackOutputs(m);E.success||y.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:m,account:e.name});const N=M(E);return I!==void 0&&((await n.hashService.persistCascadeStack(k,m,I)).success||y.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:m,account:e.name})),s.onCascadeAccountComplete?.(r,!0,void 0,t,N,!1),B({outputs:N,skipped:!1})}async function Re(o,n,c,e){const i=new Map,s=o.awsProvider.getRegion(),a=await v(o.awsProvider,n.id,s,`fjall-ipam-read-${n.name}`,e);if(!a.success)return y.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const t=new H(a.data.provider),r=j(R.PLATFORM),u=await t.getStackOutputs(r);if(!u.success)return y.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const l=/^IpamPoolId(\d{12})(\w+)$/;for(const d of u.data){const g=d.OutputKey?.match(l);if(g&&d.OutputValue){const C=`${g[1]}-${g[2]}`;i.set(C,d.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}async function we(o,n){const c=o.getDomains();if(c.length===0)return{domainsDeployed:0,errors:[]};n.onCascadePhaseStart?.("domains");const e=c.filter(t=>t.type==="apex"),i=c.filter(t=>t.type==="delegated");let s=0;const a=[];for(const t of e){const r=await o.deployDomain(t.name,n);r.success?s++:a.push(`${t.name}: ${r.error.message}`)}if(i.length>0){const t=await Promise.allSettled(i.map(r=>o.deployDomain(r.name,n)));for(let r=0;r<t.length;r++){const u=t[r],l=i[r];if(!(!u||!l))if(u.status==="fulfilled")u.value.success?s++:a.push(`${l.name}: ${u.value.error.message}`);else{const d=u.reason instanceof Error?u.reason.message:String(u.reason);a.push(`${l.name}: ${d}`)}}}return n.onCascadePhaseComplete?.("domains"),{domainsDeployed:s,errors:a}}export{J as CASCADE_MAX_CONCURRENCY,Ce as buildAccountRegionPairs,Se as buildCascadeRoleArn,Q as buildRegionList,ge as cascadeHomeRegion,Z as cascadeOperationKey,ye as deployCascadeAccount,we as deployDomains,le as partitionAccounts,he as probeCascadeRoles,Re as readPlatformIpamPoolIds};
1
+ import{join as L}from"path";import{success as B,failure as h}from"@fjall/generator";import{logger as y}from"@fjall/util/logger";import{maskSensitiveOutput as p,mapSettledWithConcurrency as U}from"@fjall/util";import{ORGANISATION_TYPES as R,getOrganisationStackName as j}from"../types/operations.js";import{CdkContextBuilder as _}from"../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as K}from"../types/deployment/index.js";import{CloudFormationService as H}from"../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as z,regionSuffix as G}from"../types/FjallState.js";import{BackupClient as Y}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as W,describeBackupVaultExists as X}from"../aws/organisations/backup.js";import{buildParamsContext as q,collectStackOutputs as M,assumeCascadeRole as v,forwardOutput as x}from"./contextHelpers.js";import{accountTier as T}from"@fjall/util";import{DEFAULT_REGION as b}from"../aws/utils/regions.js";const J=4;function le(o){const n=o.find(e=>T(e)==="platform"),c=o.filter(e=>T(e)==="account");return{platformAccount:n,memberAccounts:c}}function Q(o){const n=o?.primaryRegion??b,c=o?.secondaryRegions??[],e=new Set([n,...c]),i=o?.disasterRecoveryRegion;return i&&e.add(i),[...e]}function ge(o){return Q(o)[0]??b}function Z(o,n){return`${o} (${n})`}function Ce(o,n){const c=[];for(const e of n)for(const i of o)c.push({account:i,region:e});return c}import{buildCascadeRoleArn as $e}from"./contextHelpers.js";async function he(o,n,c,e){c.onLog?.(`Verifying cascade role access for ${n.length} account(s)\u2026`,"info");const i=await U(n,J,async a=>v(o.awsProvider,a.id,a.region??o.awsProvider.getRegion(),`fjall-preflight-${a.name}`,e)),s=[];return i.forEach((a,t)=>{const r=n[t];if(r){if(a.status==="rejected"){s.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||s.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),s.length===0&&c.onLog?.(`Cascade role access verified for ${n.length} account(s)`,"info"),s}async function ye(o,n,c,e,i,s,a){const t=a?.region??e.region??n.awsProvider.getRegion(),r=Z(e.name,t),u=a?.orgConfig??o.orgConfig,l=a?.ipamPoolId;s.onCascadeAccountStart?.(r,e.id,t,i);const d=await v(n.awsProvider,e.id,t,`fjall-cascade-${e.name}`,o.abortSignal);if(!d.success)return s.onCascadeAccountComplete?.(r,!1,p(d.error.message),t),h(new Error(`Failed to assume role for ${e.name}: ${p(d.error.message)}`));const{provider:g,credentials:C}=d.data,P=L(o.workingDirectory,"fjall",i==="platform"?R.PLATFORM:R.ACCOUNT),D=L(P,`cdk.out.${e.id}.${G(t)}`),w=_.buildDeploymentContext({deployType:i,target:c.target,path:P,assemblyDir:D,environment:e.environment??void 0,region:t,accountName:e.name,callerIdentity:K(e.id),ipamPoolId:l,...q({orgConfig:u,identity:o.identity,skipOidc:o.options?.skipOidc,region:t,primaryRegion:a?.primaryRegion,trailLifecycle:e.trailLifecycle})},{verbose:o.options?.verbose},u);if(W(e.environment,u?.disasterRecoveryRegion)){const f=await X(g.getClient(Y),o.abortSignal);if(!f.success)return s.onCascadeAccountComplete?.(r,!1,p(f.error.message),t),h(new Error(`Backup vault probe failed for ${e.name}: ${p(f.error.message)}`));w.fjallAdoptBackupVault=f.data}s.onCascadeAccountPhaseChange?.(r,"synth",t);const S=await n.cdkService.runCdkSynth(w,x(s),C);if(!S.success)return s.onCascadeAccountComplete?.(r,!1,p(`Synth failed: ${S.error}`),t),h(new Error(`Synth failed for ${e.name}: ${p(S.error)}`));const m=j(i==="platform"?R.PLATFORM:R.ACCOUNT),k=z(P,e.id,t),$=new H(g),{changed:V,currentHash:I}=await n.hashService.compareCascadeStack(D,m,k);if(!V&&o.options?.force!==!0&&await $.stackExists(m)){const f=await $.getStackOutputs(m);f.success||y.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:m,account:e.name});const F=M(f);return s.onLog?.(`${e.name}: no infrastructure changes \u2014 skipping deploy`,"info"),s.onCascadeAccountComplete?.(r,!0,void 0,t,F,!0),B({outputs:F,skipped:!0})}s.onCascadeAccountPhaseChange?.(r,"bootstrap",t);const A=await n.cdkService.runCdkBootstrap(w,x(s),C);if(!A.success)return s.onCascadeAccountComplete?.(r,!1,p(`Bootstrap failed: ${A.error}`),t),h(new Error(`Bootstrap failed for ${e.name}: ${p(A.error)}`));s.onCascadeAccountPhaseChange?.(r,"deploy",t);const O=await n.cdkService.runCdkDeploy(w,m,x(s),f=>s.onCascadeAccountResourceProgress?.(r,f,t),g,C);if(!O.success)return s.onCascadeAccountComplete?.(r,!1,p(O.error),t),h(new Error(p(O.error)));const E=await $.getStackOutputs(m);E.success||y.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:m,account:e.name});const N=M(E);return I!==void 0&&((await n.hashService.persistCascadeStack(k,m,I)).success||y.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:m,account:e.name})),s.onCascadeAccountComplete?.(r,!0,void 0,t,N,!1),B({outputs:N,skipped:!1})}async function Re(o,n,c,e){const i=new Map,s=o.awsProvider.getRegion(),a=await v(o.awsProvider,n.id,s,`fjall-ipam-read-${n.name}`,e);if(!a.success)return y.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const t=new H(a.data.provider),r=j(R.PLATFORM),u=await t.getStackOutputs(r);if(!u.success)return y.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const l=/^IpamPoolId(\d{12})(\w+)$/;for(const d of u.data){const g=d.OutputKey?.match(l);if(g&&d.OutputValue){const C=`${g[1]}-${g[2]}`;i.set(C,d.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}async function we(o,n){const c=o.getDomains();if(c.length===0)return{domainsDeployed:0,errors:[]};n.onCascadePhaseStart?.("domains");const e=c.filter(t=>t.type==="apex"),i=c.filter(t=>t.type==="delegated");let s=0;const a=[];for(const t of e){const r=await o.deployDomain(t.name,n);r.success?s++:a.push(`${t.name}: ${r.error.message}`)}if(i.length>0){const t=await Promise.allSettled(i.map(r=>o.deployDomain(r.name,n)));for(let r=0;r<t.length;r++){const u=t[r],l=i[r];if(!(!u||!l))if(u.status==="fulfilled")u.value.success?s++:a.push(`${l.name}: ${u.value.error.message}`);else{const d=u.reason instanceof Error?u.reason.message:String(u.reason);a.push(`${l.name}: ${d}`)}}}return n.onCascadePhaseComplete?.("domains"),{domainsDeployed:s,errors:a}}export{J as CASCADE_MAX_CONCURRENCY,Ce as buildAccountRegionPairs,$e as buildCascadeRoleArn,Q as buildRegionList,ge as cascadeHomeRegion,Z as cascadeOperationKey,ye as deployCascadeAccount,we as deployDomains,le as partitionAccounts,he as probeCascadeRoles,Re as readPlatformIpamPoolIds};
@@ -1 +1 @@
1
- import{isAbsolute as T,join as b,dirname as k,resolve as y}from"node:path";import{success as D,failure as S}from"@fjall/generator";import{deriveContentHashTag as v,maskSensitiveOutput as C}from"@fjall/util";import{logger as E}from"@fjall/util/logger";import{parseDockerServicesFromManifest as A}from"@fjall/util/manifest";import{STEP_IDS as N}from"../types/stepDefinitions.js";const O="dockerBuildHelper",R="Building and pushing Docker image";function w(t,n){const r=t.docker.path,e=t.docker.context,s=T(r)?r:b(n,r);return{buildContext:e?T(e)?y(e):y(n,e):y(k(s)),dockerfilePath:s,target:t.docker.target}}function I(t){return`${t.buildContext}|${t.dockerfilePath}|${t.target??""}`}function x(t){return t.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function L(t,n,r){return`${t}.dkr.ecr.${n}.${x(n)}/${r.toLowerCase()}`}function B(t,n){const r=n.name.toLowerCase(),e=n.docker.target,s=e?`-${e.toLowerCase()}`:"";return[`${t}:${r}${s}-latest`]}function _(t,n,r){const e=new Map;for(const s of t){const a=w(s,n),c=I(a),d=B(r,s),i=e.get(c);i?(i.members.push(s),i.latestTags.push(...d)):e.set(c,{identity:a,members:[s],latestTags:[...d]})}return Array.from(e.values())}async function K(t,n,r,e,s){const a=await t.buildAndPush({appName:n.appName,appPath:n.path,region:r,accountId:e},(c,d,i,p)=>{s.onDockerProgress?.(C(c),d,i,p)});return a.success?D({imageUri:a.data.imageUri,imageTag:a.data.imageTag}):S(a.error)}async function H(t,n,r,e,s,a,c){const d=t.members[0],i={appName:r.appName,appPath:t.identity.buildContext,region:e,accountId:s,buildContext:t.identity.buildContext,dockerfilePath:t.identity.dockerfilePath,imageTags:t.latestTags,serviceName:d.name,...t.identity.target!==void 0&&{target:t.identity.target},...d.docker.buildArgs!==void 0&&{buildArgs:d.docker.buildArgs}},p=await n.buildAndPush(i,(u,h,m,g)=>{c.onDockerProgress?.(C(u),h,m,g)});if(!p.success)return S(p.error);const f=p.data.imageDigest;if(typeof f!="string"||!f.startsWith("sha256:"))return S(new Error(`Buildx push succeeded but returned an invalid digest: ${C(String(f))}`));const l={},P=[];for(const u of t.members){const h=u.docker.target,m=v(f,u.name,h);if(!m.success)return S(m.error);const g=m.data,$=h?`${u.name}-${h}`:u.name;l[$]=g,P.push(`${a}:${g}`)}const o=await n.tagByDigest({sourceImage:a,digest:f,tags:P});return o.success?D({contentHashTagsByService:l}):S(o.error)}async function W(t,n,r,e){const s=t.dockerProvider;if(!s)return D({});const a=n.awsProvider.getAccountId();if(!a)return e.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),D({});const c=n.awsProvider.getRegion(),d=b(r.path,"cdk.out"),i=A(d),p=L(a,c,r.appName);E.debug(O,"runDockerBuild starting",{appName:r.appName,appPath:r.path,accountId:a,region:c,cdkOutPath:d,manifestServiceCount:i.length,manifestServices:i.map(o=>({name:o.name,path:o.docker.path,context:o.docker.context,target:o.docker.target})),stepId:N.DOCKER_OPERATIONS,stepName:R}),e.onStepStart?.(N.DOCKER_OPERATIONS,R),e.onLog?.("Initialising ECR repository\u2026","info");const f=await s.initialiseECR({appName:r.appName,region:c,accountId:a});if(f.success||e.onLog?.(`ECR initialisation failed: ${C(f.error.message)}`,"warn"),i.length===0){e.onLog?.("Building and pushing Docker image\u2026","info");const o=await K(s,r,c,a,e);if(!o.success){E.debug(O,"Docker buildAndPush (legacy) failed",{appName:r.appName,error:C(o.error.message)}),e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"error");const u=new Error(C(o.error.message));return e.onError?.(u),S(u)}return e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"completed"),e.onLog?.(`Docker image pushed: ${o.data.imageUri}`,"info"),D({})}const l=_(i,r.path,p);e.onLog?.(`Building and pushing ${i.length} service image(s) in ${l.length} group(s)\u2026`,"info");const P={};for(let o=0;o<l.length;o++){const u=l[o],h=u.members.map(g=>g.name).join(", ");e.onLog?.(`Building group ${o+1}/${l.length} (${h})\u2026`,"info");const m=await H(u,s,r,c,a,p,e);if(!m.success){E.debug(O,"Docker buildAndPush failed",{appName:r.appName,leader:u.members[0]?.name,members:h,error:C(m.error.message)}),e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"error");const g=new Error(C(m.error.message));return e.onError?.(g),S(g)}for(const[g,$]of Object.entries(m.data.contentHashTagsByService))P[g]=$}return E.debug(O,"Docker buildAndPush succeeded",{appName:r.appName,services:i.map(o=>o.name),groupCount:l.length,contentHashTags:P}),e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"completed"),e.onLog?.(`Docker images pushed for ${i.length} service(s)`,"info"),D(P)}export{R as DOCKER_BUILD_STEP_NAME,W as runDockerBuild};
1
+ import{isAbsolute as b,join as k,dirname as v,resolve as T}from"node:path";import{success as D,failure as C}from"@fjall/generator";import{deriveContentHashTag as $,maskSensitiveOutput as h}from"@fjall/util";import{logger as E}from"@fjall/util/logger";import{parseDockerServicesFromManifest as A}from"@fjall/util/manifest";import{STEP_IDS as N}from"../types/stepDefinitions.js";const O="dockerBuildHelper",R="Building and pushing Docker image";function w(t,n){const r=t.docker.path,e=t.docker.context,s=b(r)?r:k(n,r);return{buildContext:e?b(e)?T(e):T(n,e):T(v(s)),dockerfilePath:s,target:t.docker.target}}function I(t){return`${t.buildContext}|${t.dockerfilePath}|${t.target??""}`}function x(t){return t.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function L(t,n,r){return`${t}.dkr.ecr.${n}.${x(n)}/${r.toLowerCase()}`}function B(t,n){const r=n.name.toLowerCase(),e=n.docker.target,s=e?`-${e.toLowerCase()}`:"";return[`${t}:${r}${s}-latest`]}function _(t,n,r){const e=new Map;for(const s of t){const a=w(s,n),u=I(a),d=B(r,s),i=e.get(u);i?(i.members.push(s),i.latestTags.push(...d)):e.set(u,{identity:a,members:[s],latestTags:[...d]})}return Array.from(e.values())}async function K(t,n,r,e,s){const a=await t.buildAndPush({appName:n.appName,appPath:n.path,region:r,accountId:e},(u,d,i,p)=>{s.onDockerProgress?.(h(u),d,i,p)});return a.success?D({imageUri:a.data.imageUri,imageTag:a.data.imageTag}):C(a.error)}async function H(t,n,r,e,s,a,u){const d=t.members[0],i={appName:r.appName,appPath:t.identity.buildContext,region:e,accountId:s,buildContext:t.identity.buildContext,dockerfilePath:t.identity.dockerfilePath,imageTags:t.latestTags,serviceName:d.name,...t.identity.target!==void 0&&{target:t.identity.target},...d.docker.buildArgs!==void 0&&{buildArgs:d.docker.buildArgs}},p=await n.buildAndPush(i,(c,P,m,g)=>{u.onDockerProgress?.(h(c),P,m,g)});if(!p.success)return C(p.error);const f=p.data.imageDigest;if(typeof f!="string"||!f.startsWith("sha256:"))return C(new Error(`Buildx push succeeded but returned an invalid digest: ${h(String(f))}`));const l={},S=[];for(const c of t.members){const P=c.docker.target,m=$(f,c.name,P);if(!m.success)return C(m.error);const g=m.data,y=c.name;l[y]=g,S.push(`${a}:${g}`)}const o=await n.tagByDigest({sourceImage:a,digest:f,tags:S});return o.success?D({contentHashTagsByService:l}):C(o.error)}async function W(t,n,r,e){const s=t.dockerProvider;if(!s)return D({});const a=n.awsProvider.getAccountId();if(!a)return e.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),D({});const u=n.awsProvider.getRegion(),d=k(r.path,"cdk.out"),i=A(d),p=L(a,u,r.appName);E.debug(O,"runDockerBuild starting",{appName:r.appName,appPath:r.path,accountId:a,region:u,cdkOutPath:d,manifestServiceCount:i.length,manifestServices:i.map(o=>({name:o.name,path:o.docker.path,context:o.docker.context,target:o.docker.target})),stepId:N.DOCKER_OPERATIONS,stepName:R}),e.onStepStart?.(N.DOCKER_OPERATIONS,R),e.onLog?.("Initialising ECR repository\u2026","info");const f=await s.initialiseECR({appName:r.appName,region:u,accountId:a});if(f.success||e.onLog?.(`ECR initialisation failed: ${h(f.error.message)}`,"warn"),i.length===0){e.onLog?.("Building and pushing Docker image\u2026","info");const o=await K(s,r,u,a,e);if(!o.success){E.debug(O,"Docker buildAndPush (legacy) failed",{appName:r.appName,error:h(o.error.message)}),e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"error");const c=new Error(h(o.error.message));return e.onError?.(c),C(c)}return e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"completed"),e.onLog?.(`Docker image pushed: ${o.data.imageUri}`,"info"),D({})}const l=_(i,r.path,p);e.onLog?.(`Building and pushing ${i.length} service image(s) in ${l.length} group(s)\u2026`,"info");const S={};for(let o=0;o<l.length;o++){const c=l[o],P=c.members.map(g=>g.name).join(", ");e.onLog?.(`Building group ${o+1}/${l.length} (${P})\u2026`,"info");const m=await H(c,s,r,u,a,p,e);if(!m.success){E.debug(O,"Docker buildAndPush failed",{appName:r.appName,leader:c.members[0]?.name,members:P,error:h(m.error.message)}),e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"error");const g=new Error(h(m.error.message));return e.onError?.(g),C(g)}for(const[g,y]of Object.entries(m.data.contentHashTagsByService))S[g]=y}return E.debug(O,"Docker buildAndPush succeeded",{appName:r.appName,services:i.map(o=>o.name),groupCount:l.length,contentHashTags:S}),e.onStepComplete?.(N.DOCKER_OPERATIONS,R,"completed"),e.onLog?.(`Docker images pushed for ${i.length} service(s)`,"info"),D(S)}export{R as DOCKER_BUILD_STEP_NAME,W as runDockerBuild};