@fjall/deploy-core 2.4.8 → 2.5.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.
- package/dist/.minified +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +1 -1
- package/dist/src/orchestration/accountsConfig.d.ts +34 -0
- package/dist/src/orchestration/accountsConfig.js +1 -0
- package/dist/src/orchestration/dockerBuildHelper.js +1 -1
- package/dist/src/orchestration/index.d.ts +4 -0
- package/dist/src/orchestration/index.js +1 -1
- package/dist/src/orchestration/organisationDeploy.js +3 -3
- package/dist/src/orchestration/organisationSetup.js +1 -1
- package/dist/src/orchestration/reconcileProviderAccounts.d.ts +29 -0
- package/dist/src/orchestration/reconcileProviderAccounts.js +1 -0
- package/dist/src/steps/stepRegistry.js +1 -1
- package/dist/src/types/callbacks.d.ts +8 -0
- package/dist/src/types/deploymentEventSchema.d.ts +5 -1
- package/dist/src/types/deploymentEventSchema.js +1 -1
- package/dist/src/types/stepDefinitions.d.ts +4 -0
- package/dist/src/types/stepDefinitions.js +1 -1
- package/package.json +5 -4
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
118 files minified at 2026-05-29T04:05:16.658Z
|
package/dist/src/index.d.ts
CHANGED
|
@@ -43,6 +43,10 @@ export { type Result, success, failure, isSuccess, isFailure } from "@fjall/gene
|
|
|
43
43
|
export { deploy } from "./orchestration/index.js";
|
|
44
44
|
export { destroy } from "./orchestration/index.js";
|
|
45
45
|
export { partitionAccounts } from "./orchestration/index.js";
|
|
46
|
+
export { reconcileProviderAccounts, mergeReconciledProviderAccounts } from "./orchestration/index.js";
|
|
47
|
+
export type { ReconcileResult } from "./orchestration/index.js";
|
|
48
|
+
export { parseAccountsConfiguration, flattenAccountsToEnvironments, extractAllAccountNames, accountsConfigToOUTree, isStringArray, isAccountsConfig } from "./orchestration/index.js";
|
|
49
|
+
export type { AccountsConfig } from "./orchestration/index.js";
|
|
46
50
|
export { runOpenNextBuild } from "./orchestration/index.js";
|
|
47
51
|
export { runOrganisationSetup } from "./orchestration/index.js";
|
|
48
52
|
export type { OrgSetupPhase, OrgSetupCallbacks, OrgSetupConfig, OrgSetupResult, DockerProvider, DockerProgressCallback, DockerBuildParams, DockerBuildResult, ECRInitParams, ECRInitResult, TagImagesParams, TagImagesResult, TagByDigestParams, DomainDeployProvider, DomainConfig, DomainDeployResult, DeployServices } from "./orchestration/index.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as a,CASCADE_PHASES as i,CASCADE_ACCOUNT_STATUSES as
|
|
1
|
+
import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as a,CASCADE_PHASES as i,CASCADE_ACCOUNT_STATUSES as s}from"./types/index.js";import{SimpleAwsProvider as c}from"./aws/index.js";import{ensureOrganisationExists as p,describeOrganisation as S,enablePolicyTypes as l,enableServiceAccess as A,enableRamSharing as u,activateTrustedAccess as T,enableIpamDelegatedAdmin as m,updateBackupGlobalSettings as O,listAccounts as P,findAccount as d,createAccount as _,ensureOrganisationalUnitsExist as C,placeAccountsInOUs as f,buildAccountToOUMap as R,activateCostAllocationTags as N,checkIdentityCentreStatus as g,extractErrorName as x,isOULeaf as D,registerSecurityDelegates as I,SECURITY_SERVICE_PRINCIPALS as L}from"./aws/index.js";import{STEP_IDS as k,STEP_NAMES as F,INFRASTRUCTURE_STEP_NAMES as y,INFRA_STEP_NAME as U}from"./types/index.js";import{ProgressReporter as h,APPLICATION_STACKS as Y,ORGANISATION_TYPES as b,APPLICATION_DEPLOY_ORDER as G,APPLICATION_DESTROY_ORDER as B,OPENNEXT_DEPLOY_ORDER as w,OPENNEXT_DESTROY_ORDER as H,PARALLEL_DEPLOY_GROUPS as K,PARALLEL_DESTROY_GROUPS as V,OPENNEXT_PARALLEL_GROUPS as X,PARALLEL_OPERATION_TYPES as j,isApplicationOperation as q,isOrganisationOperation as z,getParallelDeployGroups as J,getParallelDestroyGroups as Q,getApplicationDeployOrder as W,getApplicationDestroyOrder as Z,getApplicationStackName as $,getOrganisationStackName as ee,isApplicationStack as re,getApplicationStepName as te,getApplicationStepId as oe,toPascalCase as ae,isOpenNextPattern as ie,OPENNEXT_PATTERNS as se,deriveResourcesFromManifestStacks as ne,STACK_NOT_FOUND_PATTERN as ce,STACK_FAILED_STATE_PATTERN as Ee,CDK_NO_STACKS_MATCH as pe,INFRASTRUCTURE_FILENAME as Se,ApplicationError as le,wrapApplicationError as Ae,FjallStateFileSchema as ue,readStateFile as Te,writeStateFile as me,createEmptyState as Oe,deleteStateFile as Pe,updateTemplateHash as de,getStateFilePath as _e,stubCallerIdentity as Ce}from"./types/index.js";import{detectPattern as Re}from"./types/index.js";import{detectPayloadPattern as ge}from"./types/index.js";import{detectDatabase as De}from"./types/index.js";import{CloudFormationEventMonitor as Le}from"./aws/index.js";import{CdkService as ke,CdkArgumentBuilder as Fe,CdkProcessManager as ye,CdkEventMonitor as Ue,startStackMonitoring as Me,DEFAULT_DEPLOY_TIMEOUT_MS as he,isCdkError as Ye,formatInfrastructureError as be,getStructuralHint as Ge,getSourceContext as Be,hasCdkDifferences as we,parseDiffOutput as He,CloudFormationService as Ke,CloudFormationError as Ve,EcsService as Xe,EcsError as je,EcsServiceResolver as qe,TemplateHashService as ze,TemplateHashError as Je,CdkContextBuilder as Qe,emitProgress as We,PROGRESS_MESSAGES as Ze,parseBuildPhase as $e,buildStepContextBuildConfig as er,convertCloudFormationOutputsToRecord as rr,ApplicationStackService as tr}from"./services/index.js";import{CdkError as ar}from"./types/errors/index.js";import{BaseServiceError as sr,ValidationError as nr,AuthError as cr,AwsError as Er,DeploymentError as pr,NetworkError as Sr,FileSystemError as lr,ConfigError as Ar,toServiceError as ur}from"./types/errors/index.js";import{filterDangerousEnvVars as mr,maskSensitiveOutput as Or,parseShellArgs as Pr,sleep as dr}from"@fjall/util";import{hasDockerfile as Cr}from"./util/dockerfileDetection.js";import{createSequencedCallbacks as Rr}from"./util/sequencedCallbacks.js";import{fileExists as gr}from"@fjall/util/fsHelpers";import{success as Dr,failure as Ir,isSuccess as Lr,isFailure as vr}from"@fjall/generator";import{deploy as Fr}from"./orchestration/index.js";import{destroy as Ur}from"./orchestration/index.js";import{partitionAccounts as hr}from"./orchestration/index.js";import{reconcileProviderAccounts as br,mergeReconciledProviderAccounts as Gr}from"./orchestration/index.js";import{parseAccountsConfiguration as wr,flattenAccountsToEnvironments as Hr,extractAllAccountNames as Kr,accountsConfigToOUTree as Vr,isStringArray as Xr,isAccountsConfig as jr}from"./orchestration/index.js";import{runOpenNextBuild as zr}from"./orchestration/index.js";import{runOrganisationSetup as Qr}from"./orchestration/index.js";import{FrameworkRegistry as Zr}from"./orchestration/index.js";import{openNextBuilder as et,dockerBuilder as rt}from"./orchestration/index.js";import{StepRegistry as ot,getDestroyStepId as at}from"./steps/index.js";export{G as APPLICATION_DEPLOY_ORDER,B as APPLICATION_DESTROY_ORDER,Y as APPLICATION_STACKS,le as ApplicationError,tr as ApplicationStackService,cr as AuthError,Er as AwsError,sr as BaseServiceError,s as CASCADE_ACCOUNT_STATUSES,i as CASCADE_PHASES,pe as CDK_NO_STACKS_MATCH,Fe as CdkArgumentBuilder,Qe as CdkContextBuilder,ar as CdkError,Ue as CdkEventMonitor,ye as CdkProcessManager,ke as CdkService,Ve as CloudFormationError,Le as CloudFormationEventMonitor,Ke as CloudFormationService,Ar as ConfigError,he as DEFAULT_DEPLOY_TIMEOUT_MS,a as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,o as DEPLOYMENT_EVENT_TYPES,pr as DeploymentError,t as DeploymentEventSchema,je as EcsError,Xe as EcsService,qe as EcsServiceResolver,lr as FileSystemError,ue as FjallStateFileSchema,Zr as FrameworkRegistry,Se as INFRASTRUCTURE_FILENAME,y as INFRASTRUCTURE_STEP_NAMES,U as INFRA_STEP_NAME,Sr as NetworkError,w as OPENNEXT_DEPLOY_ORDER,H as OPENNEXT_DESTROY_ORDER,X as OPENNEXT_PARALLEL_GROUPS,se as OPENNEXT_PATTERNS,b as ORGANISATION_TYPES,K as PARALLEL_DEPLOY_GROUPS,V as PARALLEL_DESTROY_GROUPS,j as PARALLEL_OPERATION_TYPES,Ze as PROGRESS_MESSAGES,h as ProgressReporter,L as SECURITY_SERVICE_PRINCIPALS,Ee as STACK_FAILED_STATE_PATTERN,ce as STACK_NOT_FOUND_PATTERN,k as STEP_IDS,F as STEP_NAMES,c as SimpleAwsProvider,ot as StepRegistry,Je as TemplateHashError,ze as TemplateHashService,nr as ValidationError,Vr as accountsConfigToOUTree,N as activateCostAllocationTags,T as activateTrustedAccess,R as buildAccountToOUMap,er as buildStepContextBuildConfig,g as checkIdentityCentreStatus,rr as convertCloudFormationOutputsToRecord,_ as createAccount,Oe as createEmptyState,Rr as createSequencedCallbacks,Pe as deleteStateFile,Fr as deploy,ne as deriveResourcesFromManifestStacks,S as describeOrganisation,Ur as destroy,De as detectDatabase,Re as detectPattern,ge as detectPayloadPattern,rt as dockerBuilder,We as emitProgress,m as enableIpamDelegatedAdmin,l as enablePolicyTypes,u as enableRamSharing,A as enableServiceAccess,p as ensureOrganisationExists,C as ensureOrganisationalUnitsExist,Kr as extractAllAccountNames,x as extractErrorName,Ir as failure,gr as fileExists,mr as filterDangerousEnvVars,d as findAccount,Hr as flattenAccountsToEnvironments,be as formatInfrastructureError,W as getApplicationDeployOrder,Z as getApplicationDestroyOrder,$ as getApplicationStackName,oe as getApplicationStepId,te as getApplicationStepName,at as getDestroyStepId,ee as getOrganisationStackName,J as getParallelDeployGroups,Q as getParallelDestroyGroups,Be as getSourceContext,_e as getStateFilePath,Ge as getStructuralHint,we as hasCdkDifferences,Cr as hasDockerfile,jr as isAccountsConfig,q as isApplicationOperation,re as isApplicationStack,Ye as isCdkError,vr as isFailure,D as isOULeaf,ie as isOpenNextPattern,z as isOrganisationOperation,Xr as isStringArray,Lr as isSuccess,P as listAccounts,Or as maskSensitiveOutput,Gr as mergeReconciledProviderAccounts,et as openNextBuilder,wr as parseAccountsConfiguration,$e as parseBuildPhase,He as parseDiffOutput,Pr as parseShellArgs,hr as partitionAccounts,f as placeAccountsInOUs,Te as readStateFile,br as reconcileProviderAccounts,I as registerSecurityDelegates,zr as runOpenNextBuild,Qr as runOrganisationSetup,dr as sleep,Me as startStackMonitoring,Ce as stubCallerIdentity,Dr as success,ae as toPascalCase,ur as toServiceError,O as updateBackupGlobalSettings,de as updateTemplateHash,Ae as wrapApplicationError,me as writeStateFile};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Result } from "@fjall/generator";
|
|
2
|
+
import { ConfigError } from "../types/errors/ServiceError.js";
|
|
3
|
+
import type { OUTree } from "../aws/organisations/types.js";
|
|
4
|
+
export type AccountsConfig = {
|
|
5
|
+
readonly [key: string]: string | readonly string[] | AccountsConfig;
|
|
6
|
+
};
|
|
7
|
+
export declare function isStringArray(value: unknown): value is string[];
|
|
8
|
+
export declare function isAccountsConfig(value: unknown): value is AccountsConfig;
|
|
9
|
+
/**
|
|
10
|
+
* Recursively extract account→environment pairs using leaf keys.
|
|
11
|
+
* Parent keys (e.g. "workloads") are skipped — only leaf keys become environments.
|
|
12
|
+
*/
|
|
13
|
+
export declare function flattenAccountsToEnvironments(config: AccountsConfig): Array<{
|
|
14
|
+
accountName: string;
|
|
15
|
+
environment: string;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Extract all account names from any depth of nesting.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractAllAccountNames(config: AccountsConfig): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Convert AccountsConfig to deploy-core's OUTree format for nested OU creation.
|
|
23
|
+
*/
|
|
24
|
+
export declare function accountsConfigToOUTree(config: AccountsConfig): OUTree;
|
|
25
|
+
/**
|
|
26
|
+
* Parse the ACCOUNTS configuration from `<baseDir>/fjall/organisation/infrastructure.ts`.
|
|
27
|
+
* Returns null (not an error) if the file does not exist.
|
|
28
|
+
*
|
|
29
|
+
* `baseDir` is the repository root — `process.cwd()` for the CLI (run from the
|
|
30
|
+
* user's repo) and the prepared-clone `workingDirectory` for the worker. There
|
|
31
|
+
* is intentionally no `process.cwd()` default: in the worker `process.cwd()` is
|
|
32
|
+
* the worker dir, not the clone, so callers must pass the resolved base.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseAccountsConfiguration(baseDir: string): Promise<Result<AccountsConfig | null, ConfigError>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{success as u,failure as a}from"@fjall/generator";import{maskSensitiveOutput as A,getErrorMessage as C}from"@fjall/util";import{ConfigError as f}from"../types/errors/ServiceError.js";const c="infrastructure.ts";function g(n){return Array.isArray(n)&&n.every(t=>typeof t=="string")}function p(n){return typeof n!="object"||n===null?!1:Object.entries(n).every(([t,r])=>typeof t=="string"&&(typeof r=="string"||g(r)||p(r)))}function O(n){const t=[];for(const[r,e]of Object.entries(n))if(typeof e=="string")t.push({accountName:e,environment:r});else if(Array.isArray(e))for(const o of e)t.push({accountName:o,environment:r});else typeof e=="object"&&e!==null&&t.push(...O(e));return t}function d(n){const t=[];for(const r of Object.values(n))typeof r=="string"?t.push(r):Array.isArray(r)?t.push(...r):typeof r=="object"&&r!==null&&t.push(...d(r));return t}function T(n){const t={};for(const[r,e]of Object.entries(n))typeof e=="string"?t[r]=[e]:Array.isArray(e)?t[r]=[...e]:typeof e=="object"&&e!==null&&(t[r]=T(e));return t}async function S(n){try{const t=await import("path"),r=await import("fs"),{pathToFileURL:e}=await import("url"),o=t.join(n,"fjall","organisation",c);if(!r.existsSync(o))return u(null);const{tsImport:l}=await import("tsx/esm/api"),y=e(o).href,s=await l(o,y),m=s.default,i=s.ACCOUNTS||m?.ACCOUNTS;return i?p(i)?u(i):a(new f("ACCOUNTS configuration has invalid structure","ACCOUNTS",o,{providedValue:i})):a(new f(`ACCOUNTS constant not found in organisation/${c}`,"ACCOUNTS",o,{moduleKeys:Object.keys(s),hasDefault:s.default!==void 0}))}catch(t){const r=A(C(t));return a(new f(`Failed to import ACCOUNTS from organisation/${c}: ${r}`,"ACCOUNTS",void 0,{originalError:t}))}}export{T as accountsConfigToOUTree,d as extractAllAccountNames,O as flattenAccountsToEnvironments,p as isAccountsConfig,g as isStringArray,S as parseAccountsConfiguration};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAbsolute as T,join as
|
|
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};
|
|
@@ -5,6 +5,10 @@ export { destroyOrganisation } from "./organisationDestroy.js";
|
|
|
5
5
|
export { cleanupFailedStack, isCleanableState, SAFE_CLEANUP_STATES } from "./stackCleanup.js";
|
|
6
6
|
export type { CascadeDestroyAccountResult } from "./cascadeDestroyHelpers.js";
|
|
7
7
|
export { partitionAccounts } from "./cascadeHelpers.js";
|
|
8
|
+
export { reconcileProviderAccounts, mergeReconciledProviderAccounts } from "./reconcileProviderAccounts.js";
|
|
9
|
+
export type { ReconcileResult } from "./reconcileProviderAccounts.js";
|
|
10
|
+
export { parseAccountsConfiguration, flattenAccountsToEnvironments, extractAllAccountNames, accountsConfigToOUTree, isStringArray, isAccountsConfig } from "./accountsConfig.js";
|
|
11
|
+
export type { AccountsConfig } from "./accountsConfig.js";
|
|
8
12
|
export type { DockerProvider, DockerProgressCallback, DockerServiceConfig, DockerBuildParams, DockerBuildResult, ECRInitParams, ECRInitResult, TagImagesParams, TagImagesResult, TagByDigestParams } from "./dockerInterface.js";
|
|
9
13
|
export type { DomainDeployProvider, DomainConfig, DomainDeployResult } from "./domainInterface.js";
|
|
10
14
|
export type { DeployServices } from "./serviceFactory.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{deploy as
|
|
1
|
+
import{deploy as e}from"./deploy.js";import{destroy as n}from"./destroy.js";import{deployOrganisation as i}from"./organisationDeploy.js";import{destroyOrganisation as p}from"./organisationDestroy.js";import{cleanupFailedStack as f,isCleanableState as m,SAFE_CLEANUP_STATES as u}from"./stackCleanup.js";import{partitionAccounts as l}from"./cascadeHelpers.js";import{reconcileProviderAccounts as d,mergeReconciledProviderAccounts as g}from"./reconcileProviderAccounts.js";import{parseAccountsConfiguration as y,flattenAccountsToEnvironments as C,extractAllAccountNames as O,accountsConfigToOUTree as T,isStringArray as E,isAccountsConfig as v}from"./accountsConfig.js";import{runOpenNextBuild as P}from"./openNextBuild.js";import{runOrganisationSetup as U}from"./organisationSetup.js";export*from"./builders/index.js";export{u as SAFE_CLEANUP_STATES,T as accountsConfigToOUTree,f as cleanupFailedStack,e as deploy,i as deployOrganisation,n as destroy,p as destroyOrganisation,O as extractAllAccountNames,C as flattenAccountsToEnvironments,v as isAccountsConfig,m as isCleanableState,E as isStringArray,g as mergeReconciledProviderAccounts,y as parseAccountsConfiguration,l as partitionAccounts,d as reconcileProviderAccounts,P as runOpenNextBuild,U as runOrganisationSetup};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{success as
|
|
2
|
-
`);t.onLog?.(
|
|
3
|
-
${
|
|
1
|
+
import{success as W,failure as R}from"@fjall/generator";import{OrganizationsClient as lt}from"@aws-sdk/client-organizations";import{ORGANISATION_TYPES as Y,getOrganisationStackName as X}from"../types/operations.js";import{CdkContextBuilder as pt}from"../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as mt}from"../types/deployment/index.js";import{ensureOrganisationExists as gt}from"../aws/organisations/organisation.js";import{buildParamsContext as ft,collectStackOutputs as Z,synthOrFail as tt,bootstrapOrFail as et,forwardOutput as ot,forwardResourceProgress as nt}from"./contextHelpers.js";import{partitionAccounts as rt,deployCascadeAccount as at,readPlatformIpamPoolIds as Ct,deployDomains as St}from"./cascadeHelpers.js";import{reconcileProviderAccounts as Et,mergeReconciledProviderAccounts as Ot}from"./reconcileProviderAccounts.js";import{maskSensitiveOutput as i,STRUCTURAL_ENVIRONMENTS as At}from"@fjall/util";import{INFRA_STEP_NAME as F,STEP_IDS as I,STEP_NAMES as j}from"../types/stepDefinitions.js";async function bt(n,e,r){const p=Date.now();switch(r.type){case Y.ORGANISATION:return Pt(n,e,r,p);case Y.PLATFORM:return it(n,e,r,"platform",p);case Y.ACCOUNT:return it(n,e,r,"account",p);default:{const t=r.type;return R(new Error(`Unsupported organisation type: ${String(t)}`))}}}function st(n,e,r,p,t,a){return pt.buildDeploymentContext({deployType:p,target:r.target,path:r.path,region:e.awsProvider.getRegion(),accountName:a,callerIdentity:mt(e.awsProvider.getAccountId()),orgId:t.orgId,rootId:t.rootId,managementAccountId:t.managementAccountId,...ft({orgConfig:n.orgConfig,identity:n.identity,skipOidc:n.options?.skipOidc})},{verbose:n.options?.verbose,infraOnly:n.options?.infraOnly},n.orgConfig)}async function ct(n){const e=n.awsProvider.getClient(lt),r=await gt(e);return r.success?W({orgId:r.data.orgId,rootId:r.data.rootId,managementAccountId:r.data.managementAccountId}):R(r.error)}const o={CONNECT:{id:I.CONNECT,name:F.CONNECT},PREPARE:{id:I.PREPARE_ENVIRONMENT,name:F.PREPARE},DEPLOY:{id:I.DEPLOY,name:F.DEPLOY},MONITORING:{id:I.MONITORING,name:F.MONITORING},ORG_DEPLOY:{id:I.ORG_DEPLOY,name:j.ORG_DEPLOY},CASCADE_PLATFORM:{id:I.CASCADE_PLATFORM,name:j.CASCADE_PLATFORM},CASCADE_ACCOUNTS:{id:I.CASCADE_ACCOUNTS,name:j.CASCADE_ACCOUNTS}},g=4;async function it(n,e,r,p,t){const{callbacks:a}=n;a.onStepComplete?.(o.CONNECT.id,o.CONNECT.name,"completed",0,g),a.onStepStart?.(o.PREPARE.id,o.PREPARE.name,1,g);const f=await ct(e);if(!f.success){a.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"error",1,g);const O=new Error(i(f.error.message));return a.onError?.(O),R(O)}const D=st(n,e,r,p,f.data,p==="account"?r.target:void 0);a.onLog?.(`Synthesising ${p} infrastructure\u2026`,"info");const N=await tt(e,D,a,"CDK synthesis failed");if(!N.success)return a.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"error",1,g),N;const w=await et(e,D,a);if(!w.success)return a.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"error",1,g),w;a.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"completed",1,g);const y=X(r.type);a.onStepStart?.(o.DEPLOY.id,o.DEPLOY.name,2,g);const L=await e.cdkService.runCdkDeploy(D,y,ot(a),nt(a),e.awsProvider);if(!L.success){a.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"error",2,g);const O=new Error(i(L.error));return a.onError?.(O),R(O)}a.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"completed",2,g);const k=await e.cfnService.getStackOutputs(y);k.success||a.onLog?.("Failed to read stack outputs (non-critical)","debug");const $=Z(k);return a.onStepStart?.(o.MONITORING.id,o.MONITORING.name,3,g),a.onStepComplete?.(o.MONITORING.id,o.MONITORING.name,"completed",3,g),W({target:r.target,deploymentType:"organisation",outputs:$,durationMs:Date.now()-t})}async function Pt(n,e,r,p){const{callbacks:t,options:a}=n;let f=n.orgConfig?.providerAccounts??[];if(f.length===0||f.every(s=>s.environment===At.ROOT)){const s=await Et(e,n.workingDirectory);if(s.success){const{providerAccounts:C,missingAccountNames:A}=s.data;C.length>0&&(f=Ot(n.orgConfig,C).providerAccounts,t.onLog?.(`Reconciled ${C.length} account(s) from AWS Organizations`,"info")),A.length>0&&(t.onCascadeMissingAccounts?.(A),t.onProgress?.({type:"warning",message:i(`Accounts declared in ACCOUNTS but not yet in AWS Organizations (cascade will skip): ${A.join(", ")}`)}))}else t.onProgress?.({type:"warning",message:i(`Could not reconcile accounts from AWS Organizations \u2014 cascade may skip accounts: ${s.error.message}`)})}const N=await ct(e);if(!N.success){const s=new Error(i(N.error.message));return t.onError?.(s),R(s)}const w=st(n,e,r,"organisation",N.data),y=a?.cascade!==!1,{platformAccount:L,memberAccounts:k}=rt(f),$=y&&L!==void 0?1:0,O=y&&k.length>0?1:0,c=2+$+O,{id:_,name:b}=o.PREPARE;t.onStepStart?.(_,b,0,c),t.onLog?.("Synthesising organisation infrastructure\u2026","info");const K=await tt(e,w,t,"CDK synthesis failed");if(!K.success)return t.onStepComplete?.(_,b,"error",0,c),K;const V=await et(e,w,t);if(!V.success)return t.onStepComplete?.(_,b,"error",0,c),V;t.onStepComplete?.(_,b,"completed",0,c);const{id:x,name:U}=o.ORG_DEPLOY;t.onStepStart?.(x,U,1,c);const B=X(Y.ORGANISATION),q=await e.cdkService.runCdkDeploy(w,B,ot(t),nt(t),e.awsProvider);if(!q.success){t.onStepComplete?.(x,U,"error",1,c);const s=new Error(i(q.error));return t.onError?.(s),R(s)}const H=await e.cfnService.getStackOutputs(B);H.success||t.onLog?.("Failed to read org stack outputs (non-critical)","debug");const ut=Z(H);t.onStepComplete?.(x,U,"completed",1,c);const u=[],M=[];if(y&&f.length>0){t.onCascadeStart?.();let s=0,C=0,A=!1,J=!1,P=2;const{platformAccount:S,memberAccounts:v}=rt(f);if(S){const{id:d,name:l}=o.CASCADE_PLATFORM;t.onStepStart?.(d,l,P,c),t.onCascadePhaseStart?.("platform");let E;try{E=await at(n,e,r,S,"platform",t)}catch(T){const m=i(T instanceof Error?T.message:String(T));u.push({accountId:S.id,error:m}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(d,l,"error",P,c),E=R(new Error(m))}E.success?(A=!0,E.data.outputs&&M.push({accountId:S.id,outputs:E.data.outputs}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(d,l,"completed",P,c)):u.some(T=>T.accountId===S.id)||(u.push({accountId:S.id,error:i(E.error.message)}),t.onCascadePhaseComplete?.("platform"),t.onStepComplete?.(d,l,"error",P,c)),P++}let Q=new Map;if(A&&S&&(Q=await Ct(e,S,t)),n.domainProvider){const d=await St(n.domainProvider,t);J=d.domainsDeployed>0;for(const l of d.errors)u.push({accountId:"domains",error:i(l)})}if(v.length>0){const{id:d,name:l}=o.CASCADE_ACCOUNTS;t.onStepStart?.(d,l,P,c),t.onCascadePhaseStart?.("accounts");const E=e.awsProvider.getRegion();(await Promise.allSettled(v.map(m=>{const z=E.replace(/-/g,""),h=Q.get(`${m.id}-${z}`);return at(n,e,r,m,"account",t,h)}))).forEach((m,z)=>{const h=v[z];if(!h)return;if(m.status==="rejected"){C++,u.push({accountId:h.id,error:i(m.reason instanceof Error?m.reason.message:String(m.reason))});return}const G=m.value;G.success?(s++,G.data.outputs&&M.push({accountId:h.id,outputs:G.data.outputs})):(C++,u.push({accountId:h.id,error:i(G.error.message)}))}),t.onCascadePhaseComplete?.("accounts"),t.onStepComplete?.(d,l,C===0?"completed":"error",P,c)}if(t.onCascadeComplete?.({platformDeployed:A,domainsDeployed:J,accountsDeployed:s,accountsFailed:C,errors:u}),u.length>0){const d=u.map(l=>` ${l.accountId}: ${l.error}`).join(`
|
|
2
|
+
`);t.onLog?.(i(`Cascade completed with ${u.length} failure(s):
|
|
3
|
+
${d}`),"warn")}}const dt=u.length>0?u.map(s=>i(`${s.accountId}: ${s.error}`)):void 0;return W({target:r.target,deploymentType:"organisation",outputs:ut,...M.length>0?{cascadeOutputs:M}:{},durationMs:Date.now()-p,warnings:dt})}export{bt as deployOrganisation};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{success as
|
|
1
|
+
import{success as C,failure as l}from"@fjall/generator";import{OrganizationsClient as L}from"@aws-sdk/client-organizations";import{RAMClient as D}from"@aws-sdk/client-ram";import{CloudFormationClient as F}from"@aws-sdk/client-cloudformation";import{EC2Client as j}from"@aws-sdk/client-ec2";import{BackupClient as B}from"@aws-sdk/client-backup";import{CostExplorerClient as W}from"@aws-sdk/client-cost-explorer";import{SSOAdminClient as z}from"@aws-sdk/client-sso-admin";import{ensureOrganisationExists as G}from"../aws/organisations/organisation.js";import{enablePolicyTypes as K}from"../aws/organisations/policies.js";import{enableServiceAccess as q}from"../aws/organisations/serviceAccess.js";import{enableRamSharing as H}from"../aws/organisations/ram.js";import{activateTrustedAccess as J}from"../aws/organisations/trustedAccess.js";import{enableIpamDelegatedAdmin as Q}from"../aws/organisations/ipam.js";import{updateBackupGlobalSettings as V}from"../aws/organisations/backup.js";import{listAccounts as E,createAccount as X}from"../aws/organisations/accounts.js";import{ensureOrganisationalUnitsExist as Y,placeAccountsInOUs as Z,buildAccountToOUMap as _}from"../aws/organisations/organisationalUnits.js";import{activateCostAllocationTags as $}from"../aws/organisations/costAllocation.js";import{checkIdentityCentreStatus as b}from"../aws/organisations/identityCentre.js";import{registerSecurityDelegates as k}from"../aws/organisations/delegatedAdmin.js";import{isOULeaf as ee}from"../aws/organisations/types.js";async function Ee(r,o,e){const n=[],i=[],t=[],s=[];let u;const p=r.getClient(L),O=r.getClient(D),U=r.getClient(F),R=r.getClient(j),N=r.getClient(B),T=r.getClient(W),x=r.getClient(z);e?.onPhaseStart?.("create-organisation"),e?.onProgress?.("Ensuring AWS Organisation exists");const g=await G(p);if(!g.success)return e?.onError?.("create-organisation",g.error),e?.onPhaseComplete?.("create-organisation","error"),l(g.error);const{orgId:M,rootId:P}=g.data;if(e?.onPhaseComplete?.("create-organisation","completed"),n.push("create-organisation"),await c("enable-policies",()=>(e?.onProgress?.("Enabling organisation policy types"),K(p,P)),n,t,e),await c("enable-service-access",()=>(e?.onProgress?.("Enabling AWS service access"),q(p)),n,t,e),await c("enable-ram-sharing",()=>(e?.onProgress?.("Enabling RAM sharing"),H(O)),n,t,e),await c("activate-trusted-access",()=>(e?.onProgress?.("Activating CloudFormation trusted access"),J(U)),n,t,e),o.platformAccountId){const a=o.platformAccountId;await c("enable-ipam",()=>(e?.onProgress?.("Enabling IPAM delegated administrator"),Q(R,a)),n,t,e)}await c("configure-backup",()=>(e?.onProgress?.("Updating backup global settings"),V(N)),n,t,e),e?.onPhaseStart?.("create-accounts"),e?.onProgress?.("Checking for missing accounts");const d=await te(p,o.accounts,s);let y=[];d.success?(y=d.data,n.push("create-accounts"),e?.onPhaseComplete?.("create-accounts","completed")):(t.push({phase:"create-accounts",error:d.error.message}),e?.onError?.("create-accounts",d.error),e?.onPhaseComplete?.("create-accounts","error"));let h={};e?.onPhaseStart?.("create-organisational-units"),e?.onProgress?.("Ensuring organisational units exist");const f=await Y(p,P,o.organisationalUnits);f.success?(h=f.data,n.push("create-organisational-units"),e?.onPhaseComplete?.("create-organisational-units","completed")):(t.push({phase:"create-organisational-units",error:f.error.message}),e?.onError?.("create-organisational-units",f.error),e?.onPhaseComplete?.("create-organisational-units","error"));const m=Array.isArray(o.organisationalUnits)?void 0:o.organisationalUnits,A=o.accountPlacements??[],S=A.length===0&&m!==void 0?ne(m,y):A;if(Object.keys(h).length===0)i.push("place-accounts"),e?.onPhaseStart?.("place-accounts"),e?.onPhaseComplete?.("place-accounts","skipped");else if(o.accountPlacements===void 0&&m===void 0){const a=new Error("Account placements not provided despite OUs being created. Caller must populate accountPlacements (flat-list mode cannot derive placements internally).");t.push({phase:"place-accounts",error:a.message}),e?.onPhaseStart?.("place-accounts"),e?.onError?.("place-accounts",a),e?.onPhaseComplete?.("place-accounts","error")}else if(S.length===0)i.push("place-accounts"),e?.onPhaseStart?.("place-accounts"),e?.onPhaseComplete?.("place-accounts","skipped");else{const a=m?_(m,h):void 0;await c("place-accounts",()=>(e?.onProgress?.("Placing accounts in organisational units"),Z(p,h,S,a)),n,t,e)}const w=o.costAllocationTags??[];if(w.length>0?await c("activate-cost-tags",()=>(e?.onProgress?.("Activating cost allocation tags"),$(T,w.map(a=>({TagKey:a})))),n,t,e):(i.push("activate-cost-tags"),e?.onPhaseStart?.("activate-cost-tags"),e?.onPhaseComplete?.("activate-cost-tags","skipped")),o.skipIdentityCentre)i.push("check-identity-centre"),e?.onPhaseStart?.("check-identity-centre"),e?.onPhaseComplete?.("check-identity-centre","skipped");else{e?.onPhaseStart?.("check-identity-centre"),e?.onProgress?.("Checking Identity Centre status");const a=await b(x);a.success?(u=a.data.enabled?"enabled":"not-enabled",n.push("check-identity-centre"),e?.onPhaseComplete?.("check-identity-centre","completed")):(t.push({phase:"check-identity-centre",error:a.error.message}),e?.onError?.("check-identity-centre",a.error),e?.onPhaseComplete?.("check-identity-centre","error"))}const I=o.securityDelegateAccountId;return I?await c("register-security-delegates",()=>(e?.onProgress?.("Registering security service delegated administrators"),k(p,I)),n,t,e):(i.push("register-security-delegates"),e?.onPhaseStart?.("register-security-delegates"),e?.onPhaseComplete?.("register-security-delegates","skipped")),C({organisationId:M,createdAccounts:s,identityCentreStatus:u,phasesCompleted:n,phasesSkipped:i,errors:t})}async function c(r,o,e,n,i){i?.onPhaseStart?.(r);const t=await o();t.success?(e.push(r),i?.onPhaseComplete?.(r,"completed")):(n.push({phase:r,error:t.error.message}),i?.onError?.(r,t.error),i?.onPhaseComplete?.(r,"error"))}async function te(r,o,e){const n=await E(r);if(!n.success)return l(n.error);const i=new Set(n.data.map(s=>s.Name?.toLowerCase()).filter(s=>s!==void 0));for(const s of o){if(i.has(s.name.toLowerCase()))continue;const u=await X(r,s.name,s.email);if(!u.success)return l(u.error);e.push({name:u.data.accountName,accountId:u.data.accountId})}if(e.length===0)return C(n.data);const t=await E(r);return t.success?C(t.data):l(t.error)}function v(r){const o=[];for(const e of Object.values(r))ee(e)?o.push(...e):o.push(...v(e));return o}function ne(r,o){const e=v(r),n=new Map;for(const t of o){const s=t.Name?.toLowerCase();s&&t.Id&&n.set(s,t)}const i=[];for(const t of e){const s=n.get(t.toLowerCase());s?.Id&&s.Name&&i.push({id:s.Id,name:s.Name,environment:t})}return i}export{Ee as runOrganisationSetup};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Result } from "@fjall/generator";
|
|
2
|
+
import type { ProviderAccount } from "@fjall/util/config";
|
|
3
|
+
import type { OrgConfig } from "../types/orgConfig.js";
|
|
4
|
+
import type { DeployServices } from "./serviceFactory.js";
|
|
5
|
+
export interface ReconcileResult {
|
|
6
|
+
providerAccounts: ProviderAccount[];
|
|
7
|
+
/** Account names declared in ACCOUNTS but not yet present in AWS Organizations. */
|
|
8
|
+
missingAccountNames: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* When the orgConfig providerAccounts list is empty or root-only (first-deploy
|
|
12
|
+
* or stale sync), reconstruct the deployable account set by intersecting the
|
|
13
|
+
* live AWS Organizations account list with the ACCOUNTS configuration file.
|
|
14
|
+
* The ACCOUNTS file owns the accountName→environment mapping; AWS Organizations
|
|
15
|
+
* owns account IDs.
|
|
16
|
+
*
|
|
17
|
+
* `workingDirectory` is the prepared repository root (a populated clone in the
|
|
18
|
+
* worker). It MUST be passed through — `parseAccountsConfiguration` `tsImport`s
|
|
19
|
+
* `<workingDirectory>/fjall/organisation/infrastructure.ts`, which can only
|
|
20
|
+
* resolve `@fjall/components-infrastructure` after the clone's node_modules is
|
|
21
|
+
* installed.
|
|
22
|
+
*/
|
|
23
|
+
export declare function reconcileProviderAccounts(services: DeployServices, workingDirectory: string): Promise<Result<ReconcileResult, Error>>;
|
|
24
|
+
/**
|
|
25
|
+
* Merge a reconciled providerAccounts list into an existing orgConfig.
|
|
26
|
+
* Additive by account id — the reconciler supplies entries the config is
|
|
27
|
+
* missing, never replacing pre-existing ones.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mergeReconciledProviderAccounts(existing: OrgConfig | undefined, reconciled: ProviderAccount[]): OrgConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as u}from"zod";import{success as N,failure as c}from"@fjall/generator";import{STRUCTURAL_ENVIRONMENTS as v}from"@fjall/util";import{OrganizationsClient as w}from"@aws-sdk/client-organizations";import{listAccounts as g}from"../aws/organisations/accounts.js";import{parseAccountsConfiguration as C,flattenAccountsToEnvironments as E}from"./accountsConfig.js";const T=u.object({Id:u.string().min(1),Name:u.string().min(1)});async function L(e,s){const n=await C(s);if(!n.success)return c(new Error(`Failed to parse ACCOUNTS configuration: ${n.error.message}`));if(n.data===null)return c(new Error("ACCOUNTS configuration file not found"));const r=E(n.data),f=new Map;for(const{accountName:o,environment:t}of r)f.set(o.toLowerCase(),{environment:t,displayName:o});let m;try{m=e.awsProvider.getClient(w)}catch(o){return c(o instanceof Error?o:new Error(String(o)))}const a=await g(m);if(!a.success)return c(a.error);const d=new Map;for(const o of a.data){const t=T.safeParse(o);t.success&&d.set(t.data.Name.toLowerCase(),{id:t.data.Id,name:t.data.Name})}const p=[],l=[];for(const[o,{environment:t,displayName:A}]of f){const i=d.get(o);if(!i){l.push(A);continue}t!==v.ROOT&&p.push({id:i.id,name:i.name,environment:t})}return N({providerAccounts:p,missingAccountNames:l})}function M(e,s){const n=new Map;for(const r of e?.providerAccounts??[])n.set(r.id,r);for(const r of s)n.has(r.id)||n.set(r.id,r);return{...e??{},providerAccounts:Array.from(n.values())}}export{M as mergeReconciledProviderAccounts,L as reconcileProviderAccounts};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{APPLICATION_STACKS as n,APPLICATION_DEPLOY_ORDER as A,getApplicationStepName as
|
|
1
|
+
import{APPLICATION_STACKS as n,APPLICATION_DEPLOY_ORDER as A,getApplicationStepName as t,getApplicationStepId as a}from"../types/index.js";import{STEP_IDS as e,STEP_NAMES as s,INFRA_STEP_NAME as d}from"../types/index.js";const O={Network:e.NETWORK_DESTROY,Storage:e.STORAGE_DESTROY,Messaging:e.MESSAGING_DESTROY,Database:e.DATABASE_DESTROY,Compute:e.COMPUTE_DESTROY,Cdn:e.CDN_DESTROY};function C(f){return O[f]??`${f.toLowerCase()}-destroy`}class p{static DEPLOYMENT_STEPS=new Map([["application-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.PREPARE_DEPLOY},{id:e.BOOTSTRAP,name:s.BOOTSTRAP,conditions:{requiresInfra:!0}},{id:a(n.NETWORK,"deploy"),name:t(n.NETWORK,"deploy"),conditions:{requiresInfra:!0,requiresNetwork:!0}},{id:a(n.STORAGE,"deploy"),name:t(n.STORAGE,"deploy"),conditions:{requiresInfra:!0,requiresStorage:!0}},{id:a(n.MESSAGING,"deploy"),name:t(n.MESSAGING,"deploy"),conditions:{requiresInfra:!0,requiresMessaging:!0}},{id:a(n.DATABASE,"deploy"),name:t(n.DATABASE,"deploy"),conditions:{requiresInfra:!0,requiresDatabase:!0}},{id:e.DOCKER_OPERATIONS,name:"Docker operations",conditions:{requiresDocker:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:e.TAG_ECR_IMAGES,name:"Tagging container images",conditions:{requiresImageTagging:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:a(n.COMPUTE,"deploy"),name:t(n.COMPUTE,"deploy"),conditions:{requiresInfra:!0,requiresCompute:!0}},{id:a(n.CDN,"deploy"),name:t(n.CDN,"deploy"),conditions:{requiresInfra:!0,requiresCdn:!0}}]],["application-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.CHECK_INFRA_STATE},{id:a(n.CDN,"destroy"),name:t(n.CDN,"destroy"),conditions:{requiresCdn:!0}},{id:a(n.COMPUTE,"destroy"),name:t(n.COMPUTE,"destroy")},{id:a(n.DATABASE,"destroy"),name:t(n.DATABASE,"destroy")},{id:a(n.MESSAGING,"destroy"),name:t(n.MESSAGING,"destroy"),conditions:{requiresMessaging:!0}},{id:a(n.STORAGE,"destroy"),name:t(n.STORAGE,"destroy"),conditions:{requiresStorage:!0}},{id:a(n.NETWORK,"destroy"),name:t(n.NETWORK,"destroy")}]],["organisation-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.ORG_SETUP,name:s.ORG_SETUP},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.ORG_DEPLOY,name:s.ORG_DEPLOY},{id:e.CASCADE_PLATFORM,name:s.CASCADE_PLATFORM,conditions:{requiresPlatformAccount:!0}},{id:e.CASCADE_ACCOUNTS,name:s.CASCADE_ACCOUNTS,conditions:{requiresMemberAccounts:!0}}]],["organisation-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:s.CHECK_INFRA_STATE},{id:e.CASCADE_ACCOUNTS,name:"Destroying account infrastructure",conditions:{requiresMemberAccounts:!0}},{id:e.CASCADE_PLATFORM,name:"Destroying platform infrastructure",conditions:{requiresPlatformAccount:!0}},{id:e.DESTROY,name:"Destroying organisation infrastructure"}]],["platform-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:d.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.DEPLOY,name:d.DEPLOY},{id:e.MONITORING,name:d.MONITORING}]],["platform-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying platform infrastructure"}]],["account-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:d.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:d.PREPARE},{id:e.DEPLOY,name:d.DEPLOY},{id:e.MONITORING,name:d.MONITORING}]],["account-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying account infrastructure"}]]]);static getSteps(r){if(r.deploymentType==="application"&&r.operation==="deploy"&&r.deployOnly)return[{id:e.AUTH,name:s.AUTH},{id:e.DOCKER_OPERATIONS,name:"Build and push Docker container"},{id:a(n.COMPUTE,"deploy"),name:t(n.COMPUTE,"deploy")}];const o=`${r.deploymentType}-${r.operation}`;return(this.DEPLOYMENT_STEPS.get(o)??[]).filter(i=>{if(r.deploymentType==="application"){const l=r.builderName==="opennext";if(i.conditions?.requiresInfra&&r.deployOnly)return!1;if(r.resources){const c=r.resources;if(i.conditions?.requiresNetwork&&!c.hasNetwork||i.conditions?.requiresCompute&&!c.hasCompute||i.conditions?.requiresDatabase&&!c.hasDatabase||i.conditions?.requiresStorage&&!c.hasStorage||i.conditions?.requiresMessaging&&!c.hasMessaging||i.conditions?.requiresCdn&&!c.hasCdn)return!1}else if(r.operation==="deploy"&&!l&&(i.conditions?.requiresNetwork||i.conditions?.requiresCompute||i.conditions?.requiresDatabase||i.conditions?.requiresStorage||i.conditions?.requiresMessaging||i.conditions?.requiresCdn))return!1;if(i.conditions?.requiresDocker||i.id===e.DOCKER_OPERATIONS)return l||r.infraOnly||r.resources&&!r.resources.hasCompute?!1:r.deployOnly?r.hasDockerfile===!0:r.hasDockerfile===!0||r.hasDockerfile===!1&&!r.isManagedAccount;if(i.conditions?.requiresImageTagging)return r.infraOnly||l||r.resources&&!r.resources.hasCompute?!1:r.hasDockerfile===!1}return!(i.conditions?.requiresMemberAccounts&&!r.hasMemberAccounts||i.conditions?.requiresPlatformAccount&&!r.hasPlatformAccount||i.conditions?.requiresOrgChanges&&r.hasOrgChanges!==!0||i.conditions?.requiresPlatformChanges&&r.hasPlatformChanges!==!0||i.conditions?.requiresAccountChanges&&r.hasAccountChanges!==!0||i.conditions?.requiresDomainConfiguration&&!r.hasDomainConfiguration||i.conditions?.requiresDomainChanges&&r.hasDomainChanges!==!0)}).map(i=>i.id===e.DOCKER_OPERATIONS?{...i,name:r.hasDockerfile?"Building and pushing Docker image":"Initialising container repository"}:i)}static getStepNames(r){return this.getSteps(r).map(o=>o.name)}static getStepIndex(r,o){return this.getSteps(o).findIndex(E=>E.id===r)}static getStepById(r,o="application"){const u=`${o}-deploy`,i=(this.DEPLOYMENT_STEPS.get(u)||[]).find(T=>T.id===r);if(i)return i;const l=`${o}-destroy`;return(this.DEPLOYMENT_STEPS.get(l)||[]).find(T=>T.id===r)}static isStepIncluded(r,o){return this.getSteps(o).some(E=>E.id===r)}static createContext(r,o,u){return{deploymentType:r,operation:o,deployOnly:u?.deployOnly??!1,infraOnly:u?.infraOnly??!1,isManagedAccount:u?.isManagedAccount??!1,hasDockerfile:u?.hasDockerfile??!1,pattern:u?.pattern??null,builderName:u?.builderName,resources:u?.resources}}static getDeploymentTypes(){return["application","organisation","platform","account"]}static getStackNames(r){switch(r){case"application":return[...A];case"organisation":return["Organisation"];case"platform":return["Platform"];case"account":return["Account"];default:return[]}}static getInfraStepIds(){return[e.CFN_CHECK,e.BOOTSTRAP,e.DIFF,e.NETWORK,e.STORAGE,e.MESSAGING,e.DATABASE,e.COMPUTE,e.CDN]}static getDockerStepIds(){return[e.ECR_INIT,e.DOCKER_DEPLOY]}}export{p as StepRegistry,C as getDestroyStepId};
|
|
@@ -100,6 +100,14 @@ export interface DeployCallbacks {
|
|
|
100
100
|
onCdkOutput?: (output: string, type: "synth" | "diff") => void;
|
|
101
101
|
/** @emittedBy engine */
|
|
102
102
|
onCascadeStart?: () => void;
|
|
103
|
+
/**
|
|
104
|
+
* @emittedBy engine — fired once after account reconciliation when accounts
|
|
105
|
+
* are declared in ACCOUNTS but not yet present in AWS Organizations (the
|
|
106
|
+
* cascade skips them). Carries the structured list so consumers can render a
|
|
107
|
+
* banner; the same information is also emitted as an `onProgress` warning for
|
|
108
|
+
* log surfaces.
|
|
109
|
+
*/
|
|
110
|
+
onCascadeMissingAccounts?: (accountNames: readonly string[]) => void;
|
|
103
111
|
/** @emittedBy engine */
|
|
104
112
|
onCascadePhaseStart?: (phase: CascadePhase) => void;
|
|
105
113
|
/** @emittedBy engine — fired when a cascade phase completes. */
|
|
@@ -15,7 +15,7 @@ export declare const DEPLOYMENT_EVENT_RESOURCE_CATEGORIES: readonly ["security",
|
|
|
15
15
|
export type DeploymentEventResourceCategory = (typeof DEPLOYMENT_EVENT_RESOURCE_CATEGORIES)[number];
|
|
16
16
|
export declare const CASCADE_PHASES: readonly ["platform", "domains", "accounts"];
|
|
17
17
|
export declare const CASCADE_ACCOUNT_STATUSES: readonly ["started", "deploying", "completed", "failed"];
|
|
18
|
-
export declare const DEPLOYMENT_EVENT_TYPES: readonly ["step", "resource", "docker", "ecs", "error", "complete", "cascade_phase", "cascade_account", "parallel_phase", "detection", "log"];
|
|
18
|
+
export declare const DEPLOYMENT_EVENT_TYPES: readonly ["step", "resource", "docker", "ecs", "error", "complete", "cascade_phase", "cascade_account", "cascade_missing_accounts", "parallel_phase", "detection", "log"];
|
|
19
19
|
export type DeploymentEventType = (typeof DEPLOYMENT_EVENT_TYPES)[number];
|
|
20
20
|
export type DeploymentEventCascadePhase = (typeof CASCADE_PHASES)[number];
|
|
21
21
|
export type DeploymentEventCascadeAccountStatus = (typeof CASCADE_ACCOUNT_STATUSES)[number];
|
|
@@ -29,6 +29,7 @@ export declare const DeploymentEventSchema: z.ZodObject<{
|
|
|
29
29
|
complete: "complete";
|
|
30
30
|
cascade_phase: "cascade_phase";
|
|
31
31
|
cascade_account: "cascade_account";
|
|
32
|
+
cascade_missing_accounts: "cascade_missing_accounts";
|
|
32
33
|
parallel_phase: "parallel_phase";
|
|
33
34
|
detection: "detection";
|
|
34
35
|
log: "log";
|
|
@@ -117,6 +118,9 @@ export declare const DeploymentEventSchema: z.ZodObject<{
|
|
|
117
118
|
accounts: "accounts";
|
|
118
119
|
}>>;
|
|
119
120
|
}, z.core.$strict>>;
|
|
121
|
+
cascadeMissingAccounts: z.ZodOptional<z.ZodObject<{
|
|
122
|
+
accountNames: z.ZodArray<z.ZodString>;
|
|
123
|
+
}, z.core.$strict>>;
|
|
120
124
|
parallelPhase: z.ZodOptional<z.ZodObject<{
|
|
121
125
|
stacks: z.ZodArray<z.ZodString>;
|
|
122
126
|
status: z.ZodEnum<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as e}from"zod";const r=["security","network","compute","database","storage","monitoring","dns","identity","bootstrap","events","registry","backup"],n=["platform","domains","accounts"],c=["started","deploying","completed","failed"],i=["step","resource","docker","ecs","error","complete","cascade_phase","cascade_account","parallel_phase","detection","log"],
|
|
1
|
+
import{z as e}from"zod";const r=["security","network","compute","database","storage","monitoring","dns","identity","bootstrap","events","registry","backup"],n=["platform","domains","accounts"],c=["started","deploying","completed","failed"],i=["step","resource","docker","ecs","error","complete","cascade_phase","cascade_account","cascade_missing_accounts","parallel_phase","detection","log"],m={step:"step",resource:"resource",docker:"docker",ecs:"ecs",error:"error",complete:null,cascade_phase:"cascadePhase",cascade_account:"cascadeAccount",cascade_missing_accounts:"cascadeMissingAccounts",parallel_phase:"parallelPhase",detection:"detection",log:"log"},p=e.object({type:e.enum(i),timestamp:e.string().max(64),sequence:e.number().int().nonnegative().optional(),step:e.object({id:e.string().max(256),name:e.string().max(256),index:e.number(),total:e.number(),status:e.string().max(64)}).strict().optional(),resource:e.object({logicalId:e.string().max(256),resourceType:e.string().max(256),category:e.enum(r),group:e.string().max(128).optional(),constructPath:e.string().max(512).optional(),displayName:e.string().max(256),status:e.string().max(64),statusReason:e.string().max(2048).optional(),physicalId:e.string().max(2048).optional(),expectedDurationSeconds:e.number().optional(),stack:e.string().max(256).optional()}).strict().optional(),docker:e.object({message:e.string().max(2048),percentage:e.number().optional()}).strict().optional(),ecs:e.object({status:e.string().max(64),message:e.string().max(2048).optional(),percentage:e.number().optional()}).strict().optional(),error:e.object({message:e.string().max(4096),category:e.string().max(128).optional(),remediation:e.array(e.string().max(1024)).max(10).optional()}).strict().optional(),message:e.string().max(2048).optional(),cascadePhase:e.object({phase:e.enum(n),status:e.enum(["started","completed"])}).strict().optional(),cascadeAccount:e.object({accountId:e.string().max(32),region:e.string().max(32),operationKey:e.string().max(256),status:e.enum(c),error:e.string().max(2048).optional(),phase:e.enum(["bootstrap","synth","deploy","destroy"]).optional(),cascadePhase:e.enum(n).optional()}).strict().optional(),cascadeMissingAccounts:e.object({accountNames:e.array(e.string().max(256)).max(256)}).strict().optional(),parallelPhase:e.object({stacks:e.array(e.string().max(256)).max(20),status:e.enum(["started","completed"]),results:e.array(e.object({stack:e.string().max(256),success:e.boolean(),error:e.string().max(2048).optional()}).strict()).max(20).optional()}).strict().optional(),detection:e.object({pattern:e.string().max(128).nullable(),hasDockerfile:e.boolean(),hasDifferences:e.boolean(),resources:e.object({hasNetwork:e.boolean(),hasCompute:e.boolean(),hasDatabase:e.boolean(),hasStorage:e.boolean(),hasMessaging:e.boolean(),hasCdn:e.boolean()}).strict(),requiredSecrets:e.array(e.string().max(512)).max(100).optional()}).strict().optional(),log:e.object({message:e.string().max(2048),level:e.enum(["info","debug","warn"])}).strict().optional()}).strict().superRefine((t,s)=>{const a=m[t.type],o=a?t[a]:void 0;a&&o==null&&s.addIssue({code:e.ZodIssueCode.custom,message:`"${a}" is required when type is "${t.type}"`,path:[a]}),t.type==="complete"&&!t.message&&s.addIssue({code:e.ZodIssueCode.custom,message:'"message" is required when type is "complete"',path:["message"]})});function u(t){if(t==="platform")return"platform";if(t==="account")return"accounts"}export{c as CASCADE_ACCOUNT_STATUSES,n as CASCADE_PHASES,r as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,i as DEPLOYMENT_EVENT_TYPES,p as DeploymentEventSchema,u as toCascadePhase};
|
|
@@ -73,6 +73,10 @@ export declare const STEP_NAMES: {
|
|
|
73
73
|
readonly PREPARE_DESTROY: "Preparing destruction";
|
|
74
74
|
readonly BOOTSTRAP: "Bootstrapping AWS environment";
|
|
75
75
|
readonly CHECK_INFRA_STATE: "Checking infrastructure state";
|
|
76
|
+
readonly ORG_SETUP: "Configuring organisation";
|
|
77
|
+
readonly ORG_DEPLOY: "Deploying organisation";
|
|
78
|
+
readonly CASCADE_PLATFORM: "Deploying platform";
|
|
79
|
+
readonly CASCADE_ACCOUNTS: "Deploying accounts";
|
|
76
80
|
};
|
|
77
81
|
/**
|
|
78
82
|
* Canonical step names emitted by infrastructure deployments (platform/account).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const E={AUTH:"auth",DETECT_CONFIG:"detect-config",BOOTSTRAP:"bootstrap",CFN_CHECK:"cfn-check",DIFF:"diff",DEPLOY:"deploy",DESTROY:"destroy",VERIFY:"verify",CDK_SYNTH:"cdk-synth",DOCKER_OPERATIONS:"docker-operations",ECR_INIT:"ecr-init",DOCKER_DEPLOY:"docker-deploy",TAG_ECR_IMAGES:"tag-ecr-images",ORG_SETUP:"org-setup",ORG_ENSURE:"org-ensure",ORG_POLICY_TYPES:"policy-types",ORG_SERVICE_ACCESS:"service-access",ORG_CREATE_ACCOUNTS:"create-accounts",ORG_ENSURE_OUS:"ensure-ous",ORG_PLACE_ACCOUNTS:"place-accounts",IDENTITY_CENTRE:"identity-centre",ORG_ACCOUNTS:"accounts",ORG_COST_TAGS:"cost-tags",ORG_PROFILES:"profiles",PREPARE:"prepare",PREPARE_ENVIRONMENT:"prepare-environment",PLATFORM_ACCOUNT:"platform-account",ACCOUNT_CONTEXT:"account-context",CONNECT:"connect",MONITORING:"monitoring",ORG_DEPLOY:"organisation-deploy",ORG_DESTROY:"organisation-destroy",CASCADE_PLATFORM:"cascade-platform",CASCADE_DOMAINS:"cascade-domains",CASCADE_ACCOUNTS:"cascade-accounts",NETWORK:"network",STORAGE:"storage",MESSAGING:"messaging",DATABASE:"database",COMPUTE:"compute",CDN:"cdn",NETWORK_DESTROY:"network-destroy",STORAGE_DESTROY:"storage-destroy",MESSAGING_DESTROY:"messaging-destroy",DATABASE_DESTROY:"database-destroy",COMPUTE_DESTROY:"compute-destroy",CDN_DESTROY:"cdn-destroy"},t={AUTH:"Authenticating with AWS",PREPARE_DEPLOY:"Preparing deployment",PREPARE_DESTROY:"Preparing destruction",BOOTSTRAP:"Bootstrapping AWS environment",CHECK_INFRA_STATE:"Checking infrastructure state"},e=["Connect securely","Prepare environment","Deploy infrastructure","Enable monitoring"],o={CONNECT:e[0],PREPARE:e[1],DEPLOY:e[2],MONITORING:e[3]};export{e as INFRASTRUCTURE_STEP_NAMES,o as INFRA_STEP_NAME,E as STEP_IDS,t as STEP_NAMES};
|
|
1
|
+
const E={AUTH:"auth",DETECT_CONFIG:"detect-config",BOOTSTRAP:"bootstrap",CFN_CHECK:"cfn-check",DIFF:"diff",DEPLOY:"deploy",DESTROY:"destroy",VERIFY:"verify",CDK_SYNTH:"cdk-synth",DOCKER_OPERATIONS:"docker-operations",ECR_INIT:"ecr-init",DOCKER_DEPLOY:"docker-deploy",TAG_ECR_IMAGES:"tag-ecr-images",ORG_SETUP:"org-setup",ORG_ENSURE:"org-ensure",ORG_POLICY_TYPES:"policy-types",ORG_SERVICE_ACCESS:"service-access",ORG_CREATE_ACCOUNTS:"create-accounts",ORG_ENSURE_OUS:"ensure-ous",ORG_PLACE_ACCOUNTS:"place-accounts",IDENTITY_CENTRE:"identity-centre",ORG_ACCOUNTS:"accounts",ORG_COST_TAGS:"cost-tags",ORG_PROFILES:"profiles",PREPARE:"prepare",PREPARE_ENVIRONMENT:"prepare-environment",PLATFORM_ACCOUNT:"platform-account",ACCOUNT_CONTEXT:"account-context",CONNECT:"connect",MONITORING:"monitoring",ORG_DEPLOY:"organisation-deploy",ORG_DESTROY:"organisation-destroy",CASCADE_PLATFORM:"cascade-platform",CASCADE_DOMAINS:"cascade-domains",CASCADE_ACCOUNTS:"cascade-accounts",NETWORK:"network",STORAGE:"storage",MESSAGING:"messaging",DATABASE:"database",COMPUTE:"compute",CDN:"cdn",NETWORK_DESTROY:"network-destroy",STORAGE_DESTROY:"storage-destroy",MESSAGING_DESTROY:"messaging-destroy",DATABASE_DESTROY:"database-destroy",COMPUTE_DESTROY:"compute-destroy",CDN_DESTROY:"cdn-destroy"},t={AUTH:"Authenticating with AWS",PREPARE_DEPLOY:"Preparing deployment",PREPARE_DESTROY:"Preparing destruction",BOOTSTRAP:"Bootstrapping AWS environment",CHECK_INFRA_STATE:"Checking infrastructure state",ORG_SETUP:"Configuring organisation",ORG_DEPLOY:"Deploying organisation",CASCADE_PLATFORM:"Deploying platform",CASCADE_ACCOUNTS:"Deploying accounts"},e=["Connect securely","Prepare environment","Deploy infrastructure","Enable monitoring"],o={CONNECT:e[0],PREPARE:e[1],DEPLOY:e[2],MONITORING:e[3]};export{e as INFRASTRUCTURE_STEP_NAMES,o as INFRA_STEP_NAME,E as STEP_IDS,t as STEP_NAMES};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/deploy-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Shared deployment engine for Fjall — used by CLI and webapp worker",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -73,14 +73,15 @@
|
|
|
73
73
|
"@aws-sdk/client-s3": "^3.1038.0",
|
|
74
74
|
"@aws-sdk/client-sso-admin": "^3.1038.0",
|
|
75
75
|
"@aws-sdk/client-sts": "^3.1038.0",
|
|
76
|
-
"@fjall/generator": "^2.
|
|
77
|
-
"@fjall/util": "^
|
|
76
|
+
"@fjall/generator": "^2.5.0",
|
|
77
|
+
"@fjall/util": "^2.5.0",
|
|
78
78
|
"@smithy/node-http-handler": "^4.6.1",
|
|
79
|
+
"tsx": "^4.21.0",
|
|
79
80
|
"zod": "^4.4.3"
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
83
|
"@types/node": "^25.6.0",
|
|
83
84
|
"vitest": "^4.1.5"
|
|
84
85
|
},
|
|
85
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "5c8f0e004f5520c692f2ee2063c3558c2451f2cf"
|
|
86
87
|
}
|