@fjall/deploy-core 12.0.0 → 12.1.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.
@@ -9,7 +9,7 @@
9
9
  "package.json",
10
10
  "tsconfig.json"
11
11
  ],
12
- "hash": "919770f346bd0ee85146c3daf92b3dd26b110a0e4bc628196cedb791bb130c21",
12
+ "hash": "4250aea204bbe1e15ed014e7778bdda555ef84c50eff9e54b4d5dfa036b62e53",
13
13
  "files": {
14
14
  "src/__test-utils__/awsMockHelpers.ts": "fea371c1b1869678",
15
15
  "src/__test-utils__/index.ts": "e03cf35821157dc4",
@@ -69,7 +69,7 @@
69
69
  "src/orchestration/application/deploySessionPolicy.ts": "8052c64ce050c2b8",
70
70
  "src/orchestration/application/destroyPreFlight.ts": "cc4978dec60b033e",
71
71
  "src/orchestration/application/destructionGate.ts": "3b64832555e15887",
72
- "src/orchestration/application/detectionPipeline.ts": "580a62c8093f96ce",
72
+ "src/orchestration/application/detectionPipeline.ts": "69b7dfcedf22fe01",
73
73
  "src/orchestration/application/dockerBuildHelper.ts": "e7a7259e987d93be",
74
74
  "src/orchestration/application/ecrCacheRepository.ts": "d7a5602ccabde493",
75
75
  "src/orchestration/application/ecsDeploymentTail.ts": "d9ff18631984b2e5",
@@ -100,7 +100,7 @@
100
100
  "src/orchestration/builders/index.ts": "3b0b2492294c62db",
101
101
  "src/orchestration/builders/openNextBuilder.ts": "7416a37e19380660",
102
102
  "src/orchestration/builders/staticSiteBuilder.ts": "a0403be58052310f",
103
- "src/orchestration/contextHelpers.ts": "b38af2503a974f16",
103
+ "src/orchestration/contextHelpers.ts": "4f8e7903cb79d9fe",
104
104
  "src/orchestration/deploy.ts": "5cbdc229366ea1e6",
105
105
  "src/orchestration/deployEnvironment.ts": "acfddd575949b7ca",
106
106
  "src/orchestration/destroy.ts": "b6da154984e63a8d",
@@ -128,8 +128,8 @@
128
128
  "src/orchestration/index.ts": "0b230a38e1ef50d2",
129
129
  "src/orchestration/manifestSecretParser.ts": "c5576bd88a3652f8",
130
130
  "src/orchestration/organisation/accountsConfig.ts": "a55a3306564e7b2f",
131
- "src/orchestration/organisation/cascadeDestroyHelpers.ts": "46ca8e144ea76db2",
132
- "src/orchestration/organisation/cascadeHelpers.ts": "d62c4c3b385f9c17",
131
+ "src/orchestration/organisation/cascadeDestroyHelpers.ts": "1dddeae830fbb549",
132
+ "src/orchestration/organisation/cascadeHelpers.ts": "97f7f12727b8c0ed",
133
133
  "src/orchestration/organisation/cascadeSummary.ts": "335be9649cea0626",
134
134
  "src/orchestration/organisation/organisationDeploy.ts": "f3fad626230d8a16",
135
135
  "src/orchestration/organisation/organisationDestroy.ts": "06076a7e76345854",
@@ -190,8 +190,8 @@
190
190
  "src/services/infrastructure/CdkEventMonitoring.ts": "f172272948a5db40",
191
191
  "src/services/infrastructure/CdkOutputAnalyser.ts": "2e7cb06b6554de84",
192
192
  "src/services/infrastructure/CdkOutputParser.ts": "eed9f3475324118e",
193
- "src/services/infrastructure/CdkProcessManager.ts": "f68ae4e44adee2fa",
194
- "src/services/infrastructure/CdkService.ts": "9494e7b64b3dd9f3",
193
+ "src/services/infrastructure/CdkProcessManager.ts": "8e1237546cebb2a4",
194
+ "src/services/infrastructure/CdkService.ts": "954ac16a4c646912",
195
195
  "src/services/infrastructure/CdkServiceTypes.ts": "c4f3075238952728",
196
196
  "src/services/infrastructure/CfnRegistryService.ts": "bd4f57d97982cdda",
197
197
  "src/services/infrastructure/CloudFormationService.ts": "82ce03614feae2db",
@@ -207,7 +207,7 @@
207
207
  "src/services/infrastructure/cfnRegistrySeed.ts": "b1232b66883d6f90",
208
208
  "src/services/infrastructure/changeSetProbe.ts": "5f3e100db396cf9b",
209
209
  "src/services/infrastructure/constructMapEnrichment.ts": "7400af9bcfaa0da3",
210
- "src/services/infrastructure/dependencyIntegrityGate.ts": "063a3539a0fa9a86",
210
+ "src/services/infrastructure/dependencyIntegrityGate.ts": "c6c362e3ef75bbb4",
211
211
  "src/services/infrastructure/index.ts": "c45e43711bf64fa9",
212
212
  "src/services/infrastructure/infraTypecheckPreflight.ts": "09e423a8283e3b8b",
213
213
  "src/services/infrastructure/transientNetworkRecovery.ts": "264b9a16fea86869",
@@ -227,7 +227,7 @@
227
227
  "src/types/config/DomainState.ts": "76a076a1735ac6cd",
228
228
  "src/types/config/FjallState.ts": "e586d9c72c177dea",
229
229
  "src/types/config/entitlements.ts": "f69ad28847b67e9a",
230
- "src/types/config/orgConfig.ts": "452866f049cbd71d",
230
+ "src/types/config/orgConfig.ts": "4ca02bb3097fa3f2",
231
231
  "src/types/constants.ts": "4d77e1ab4925b9a6",
232
232
  "src/types/credentials.ts": "73ea792d58371b99",
233
233
  "src/types/deployEmitter.ts": "b4e0b9659fa7ecc3",
@@ -235,7 +235,7 @@
235
235
  "src/types/deployEventParse.ts": "16bc2371fb747d96",
236
236
  "src/types/deployEventTypeGuards.ts": "731c4e5761b13fca",
237
237
  "src/types/deployment/DeploymentServiceTypes.ts": "ba257f2e37013afd",
238
- "src/types/deployment/DeploymentTypes.ts": "795eb2bccf126f59",
238
+ "src/types/deployment/DeploymentTypes.ts": "fc7cf47cd1061408",
239
239
  "src/types/deployment/cloudformation.ts": "5a0c51e263827943",
240
240
  "src/types/deployment/index.ts": "4bdffd195379a05d",
241
241
  "src/types/deployment/parallel.ts": "c06e3ac55fb39eb7",
@@ -262,9 +262,9 @@
262
262
  "src/util/sleepAbortable.ts": "f9cac3bf8a79a476",
263
263
  "src/version.ts": "1811a4475823b27e",
264
264
  "../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
265
- "package.json": "0aae2bf9113c3ee9",
265
+ "package.json": "694bf1ca9d5112b6",
266
266
  "tsconfig.json": "1929cf3f99fe9b1e"
267
267
  },
268
- "outputHash": "aa91a69b670b74770999c2c9467ab76d904a6a9281583899e5050bc60620a974",
268
+ "outputHash": "97e2ceb8fcd71d8df97284f3093522a76bc4aec4d7b5ff5c3013fc2069cabca1",
269
269
  "outputFileCount": 493
270
270
  }
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 246 files minified at 2026-08-13T06:27:15.715Z
1
+ 246 files minified at 2026-08-15T06:01:57.084Z
@@ -1 +1 @@
1
- var H=Object.defineProperty;var S=(o,r)=>H(o,"name",{value:r,configurable:!0});import{join as M}from"path";import{success as O,failure as s}from"@fjall/generator";import{logger as L}from"@fjall/util/logger";import{maskSensitiveOutput as n}from"@fjall/util";import{deriveResourcesFromManifestStacks as W}from"../../types/detection/patternDetection.js";import{emitProgress as E,PROGRESS_MESSAGES as A}from"../../services/supporting/helpers.js";import{parseRequiredSecretsFromManifest as q,parseImportedSecretNamesFromManifest as B}from"../manifestSecretParser.js";import{resolveSecretArns as I}from"../secretArnResolver.js";import{isAborted as T}from"../../aws/organisations/types.js";import{parseDockerDeclarationsFromManifest as G}from"@fjall/util/manifest";import{assertCapacityPinsSupportedByAssembly as K,assertEngineCompatibleWithAssembly as j}from"./engineCompat.js";async function te(o,r,i,e,y){e.onDetectionPhaseChange?.("detect","started");const C=r.frameworkRegistry.resolve({appPath:o.path}),w=C?.detection.pattern??null,N=C?.detection.hasDatabase??!1;e.onLog?.(`Pattern detected: ${w??"standard"}`,"info"),e.onDetectionPhaseChange?.("detect","completed");const a=M(o.path,"cdk.out");e.onDetectionPhaseChange?.("synth","started"),E(e,A.SYNTH);const l=await r.cdkService.runCdkSynth(i,t=>e.onCdkOutput?.(t,"synth"));if(!l.success)return s(new Error(n(`CDK synthesis failed: ${l.error}`)));if(e.onDetectionPhaseChange?.("synth","completed"),i.resolvedSecretArns===void 0){const t=B(a);if(t.length>0){e.onLog?.(`Resolving ${t.length} imported secret ARN(s)\u2026`,"info");const c=await I(r.awsProvider,t);if(!c.success)return s(new Error(n(c.error.message)));i.resolvedSecretArns=JSON.stringify(c.data);const g=await r.cdkService.runCdkSynth(i,F=>e.onCdkOutput?.(F,"synth"));if(!g.success)return s(new Error(n(`CDK synthesis failed: ${g.error}`)))}}const D=j({cdkOutPath:a,onWarning:S(t=>e.onLog?.(t,"warn"),"onWarning")});if(!D.success)return s(new Error(n(D.error.message)));const P=K({cdkOutPath:a,capacityIdentityWire:i.capacityIdentity,appName:o.appName});if(!P.success)return s(new Error(n(P.error.message)));const m=q(a);m.length>0&&e.onLog?.(`Found ${m.length} required secret path(s) in manifest`,"info"),e.onDetectionPhaseChange?.("hash","started"),E(e,A.HASH);const d=await r.hashService.getTemplateHashes(a);if(!d.success)return s(new Error(n(`Template hash computation failed: ${d.error.message}`)));const u=d.data,p=await r.hashService.compareWithState(u,o.path);if(!p.success)return s(new Error(n(`Hash comparison failed: ${p.error.message}`)));const h=p.data;e.onDetectionPhaseChange?.("hash","completed");const f=new Map(h.stackChanges);for(const[t,c]of h.stackChanges){if(T(y))break;c||await r.cfnService.stackExists(t,void 0,y)||(L.debug("detectionPipeline","Stale hash detected \u2014 stack missing in CFN",{stackName:t}),f.set(t,!0))}const k=Array.from(u.keys()),R=W(k),v=R.hasCompute&&G(a).declaresBuild,$=Array.from(f.values()).some(Boolean);return e.onLog?.(`Detection complete: ${h.changedCount} changed, ${h.unchangedCount} unchanged`,"info"),O({pattern:w,hasDatabase:N,hasDifferences:$,stackChanges:f,currentHashes:u,resources:R,hasDockerfile:v,requiredSecrets:m})}S(te,"runDetectionPipeline");export{te as runDetectionPipeline};
1
+ var M=Object.defineProperty;var y=(o,r)=>M(o,"name",{value:r,configurable:!0});import{join as O}from"path";import{success as L,failure as s}from"@fjall/generator";import{logger as W}from"@fjall/util/logger";import{maskSensitiveOutput as n}from"@fjall/util";import{deriveResourcesFromManifestStacks as q}from"../../types/detection/patternDetection.js";import{emitProgress as E,PROGRESS_MESSAGES as A}from"../../services/supporting/helpers.js";import{parseRequiredSecretsFromManifest as B,parseImportedSecretNamesFromManifest as I}from"../manifestSecretParser.js";import{resolveSecretArns as T}from"../secretArnResolver.js";import{emitStepWarnings as N}from"../contextHelpers.js";import{isAborted as G}from"../../aws/organisations/types.js";import{parseDockerDeclarationsFromManifest as K}from"@fjall/util/manifest";import{assertCapacityPinsSupportedByAssembly as j,assertEngineCompatibleWithAssembly as J}from"./engineCompat.js";async function se(o,r,i,e,C){e.onDetectionPhaseChange?.("detect","started");const w=r.frameworkRegistry.resolve({appPath:o.path}),D=w?.detection.pattern??null,v=w?.detection.hasDatabase??!1;e.onLog?.(`Pattern detected: ${D??"standard"}`,"info"),e.onDetectionPhaseChange?.("detect","completed");const a=O(o.path,"cdk.out");e.onDetectionPhaseChange?.("synth","started"),E(e,A.SYNTH);const d=await r.cdkService.runCdkSynth(i,t=>e.onCdkOutput?.(t,"synth"));if(!d.success)return s(new Error(n(`CDK synthesis failed: ${d.error}`)));if(N(e,d.data),e.onDetectionPhaseChange?.("synth","completed"),i.resolvedSecretArns===void 0){const t=I(a);if(t.length>0){e.onLog?.(`Resolving ${t.length} imported secret ARN(s)\u2026`,"info");const c=await T(r.awsProvider,t);if(!c.success)return s(new Error(n(c.error.message)));i.resolvedSecretArns=JSON.stringify(c.data);const h=await r.cdkService.runCdkSynth(i,H=>e.onCdkOutput?.(H,"synth"));if(!h.success)return s(new Error(n(`CDK synthesis failed: ${h.error}`)));N(e,h.data)}}const P=J({cdkOutPath:a,onWarning:y(t=>e.onLog?.(t,"warn"),"onWarning")});if(!P.success)return s(new Error(n(P.error.message)));const R=j({cdkOutPath:a,capacityIdentityWire:i.capacityIdentity,appName:o.appName});if(!R.success)return s(new Error(n(R.error.message)));const u=B(a);u.length>0&&e.onLog?.(`Found ${u.length} required secret path(s) in manifest`,"info"),e.onDetectionPhaseChange?.("hash","started"),E(e,A.HASH);const p=await r.hashService.getTemplateHashes(a);if(!p.success)return s(new Error(n(`Template hash computation failed: ${p.error.message}`)));const f=p.data,g=await r.hashService.compareWithState(f,o.path);if(!g.success)return s(new Error(n(`Hash comparison failed: ${g.error.message}`)));const m=g.data;e.onDetectionPhaseChange?.("hash","completed");const S=new Map(m.stackChanges);for(const[t,c]of m.stackChanges){if(G(C))break;c||await r.cfnService.stackExists(t,void 0,C)||(W.debug("detectionPipeline","Stale hash detected \u2014 stack missing in CFN",{stackName:t}),S.set(t,!0))}const $=Array.from(f.keys()),l=q($),k=l.hasCompute&&K(a).declaresBuild,F=Array.from(S.values()).some(Boolean);return e.onLog?.(`Detection complete: ${m.changedCount} changed, ${m.unchangedCount} unchanged`,"info"),L({pattern:D,hasDatabase:v,hasDifferences:F,stackChanges:S,currentHashes:f,resources:l,hasDockerfile:k,requiredSecrets:u})}y(se,"runDetectionPipeline");export{se as runDetectionPipeline};
@@ -6,7 +6,7 @@ import type { OrgConfig } from "../types/config/orgConfig.js";
6
6
  import type { DeployIdentity } from "../types/credentials.js";
7
7
  import type { AwsProvider, AwsProviderCredentials } from "../aws/AwsProvider.js";
8
8
  import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
9
- import type { DeploymentContext } from "../types/deployment/DeploymentTypes.js";
9
+ import type { DeploymentContext, StepOutput } from "../types/deployment/DeploymentTypes.js";
10
10
  import type { DeployCallbacks } from "../types/callbacks.js";
11
11
  import type { DeployServices } from "./serviceFactory.js";
12
12
  /**
@@ -130,6 +130,15 @@ export declare function reconcileOidcProviderIfCarried(context: SynthOidcContext
130
130
  export declare function forwardOutput(callbacks: DeployCallbacks): (chunk: string) => void;
131
131
  /** Forward onResourceProgress callback — reduces lambda repetition across orchestration files. */
132
132
  export declare function forwardResourceProgress(callbacks: DeployCallbacks): (event: ResourceEvent) => void;
133
+ /**
134
+ * Re-emit a successful step's degradation warnings (`StepOutput.warnings`,
135
+ * pre-masked at construction) as typed progress events. The raw
136
+ * onOutput/onCdkOutput chunk channel lands in debug logging on the TUI,
137
+ * agent and piped-human surfaces, so every step-running caller that holds
138
+ * DeployCallbacks must route the payload's warnings through here or the
139
+ * degrade is invisible (read-failure posture ADR clause 2).
140
+ */
141
+ export declare function emitStepWarnings(callbacks: DeployCallbacks, step: StepOutput | undefined): void;
133
142
  /** Convert CloudFormation getStackOutputs result to Record<string, string>. */
134
143
  export declare function collectStackOutputs(result: Result<Array<{
135
144
  OutputKey?: string;
@@ -1 +1 @@
1
- var w=Object.defineProperty;var n=(e,r)=>w(e,"name",{value:r,configurable:!0});import{join as R}from"path";import{success as p,failure as s}from"@fjall/generator";import{getErrorMessage as A,maskSensitiveOutput as a,projectCapacityIdentityToTarget as S,sleep as E}from"@fjall/util";import{logger as T}from"@fjall/util/logger";import{IAMClient as b}from"@aws-sdk/client-iam";import{hasOrganisationTierAccount as h}from"../aws/organisations/accountGlobals.js";import{reconcileOidcProviderAfterDeploy as _}from"../aws/organisations/oidcProvider.js";import{SimpleAwsProvider as $}from"../aws/SimpleAwsProvider.js";import{assertEngineCompatibleWithAssembly as v}from"./application/engineCompat.js";const K={account:"active",draining:"draining",org:"removed"};function j(e,r){return e!==void 0&&e!==""&&r!==void 0&&r!==""&&e!==r}n(j,"targetsNonPrimaryRegion");function X(e){const r=j(e.region,e.primaryRegion);return{...e.orgConfig!==void 0?{orgConfig:JSON.stringify(e.orgConfig)}:{},...e.identity!==void 0?{fjallOrgId:e.identity.fjallOrgId}:{},...e.skipOidc?{fjallOidcConfigured:!0}:{},...r?{fjallAccountGlobalsConfigured:!0}:{},...e.trailLifecycle!==void 0?{fjallAccountTrailState:K[e.trailLifecycle]}:{}}}n(X,"buildParamsContext");function q(e){if(e.capacityIdentity===void 0||e.deployTargetName===void 0)return{};const r=S(e.capacityIdentity,e.deployTargetName);return r!==void 0?{capacityIdentity:JSON.stringify(r)}:{}}n(q,"buildCapacityIdentityContext");function H(e,r,t,o){if(t!==void 0)return t;if(e==="account")return h(r?.providerAccounts)?o===!0:!0}n(H,"resolveAccountBootstrapSkipOidc");function x(e){return e.fjallOrgId!==void 0&&e.fjallOrgId!==""&&e.fjallOidcConfigured!==!0&&e.fjallAccountGlobalsConfigured!==!0}n(x,"synthCarriedOidcConnector");async function Q(e,r,t,o){x(e)&&await _(r.getClient(b),t,o)}n(Q,"reconcileOidcProviderIfCarried");function I(e){return r=>e.onOutput?.(r)}n(I,"forwardOutput");function Z(e){return r=>e.onResourceProgress?.(r)}n(Z,"forwardResourceProgress");function ee(e){if(!e.success||e.data.length===0)return;const r={};for(const t of e.data)t.OutputKey&&t.OutputValue!==void 0&&(r[t.OutputKey]=t.OutputValue);return Object.keys(r).length>0?r:void 0}n(ee,"collectStackOutputs");const C="OrganizationAccountAccessRole",y=5,M=5e3,D=3e4;function L(e){return`arn:aws:iam::${e}:role/${C}`}n(L,"buildCascadeRoleArn");function P(e,r){return r===void 0?E(e):r.aborted?Promise.resolve():new Promise(t=>{const o=n(()=>{t()},"onAbort");r.addEventListener("abort",o,{once:!0}),E(e).then(()=>{r.removeEventListener("abort",o),t()})})}n(P,"sleepAbortable");async function re(e,r,t,o,u){if(!e.assumeRole)return s(new Error("AwsProvider does not support assumeRole"));const d=L(r),i=e.assumeRole.bind(e);let c;for(let f=0;f<=y;f++)try{c=await i(d,o);break}catch(l){const m=l instanceof Error?l.name:void 0;if(m==="AccessDenied"||m==="AccessDeniedException")return s(new Error(`Access denied assuming ${C} in account ${r}. The role may not exist or may not trust the management account.`));if(f<y){const g=Math.min(M*2**f,D);if(T.debug("assumeCascadeRole",`Attempt ${f+1} failed for account ${r}, retrying in ${Math.round(g/1e3)}s`,{error:a(A(l))}),await P(g,u),u?.aborted)return s(new Error(`Aborted while retrying assume-role for account ${r}`));continue}return s(new Error(`Failed to assume role in account ${r} after ${y+1} attempts: ${a(A(l))}`))}if(!c)return s(new Error(`Failed to assume role in account ${r}`));const O=new $({accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,sessionToken:c.sessionToken,region:t,accountId:r});return p({provider:O,credentials:{accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,sessionToken:c.sessionToken}})}n(re,"assumeCascadeRole");function k(e,r){const t=v({cdkOutPath:e,onWarning:n(o=>r.onLog?.(o,"warn"),"onWarning")});return t.success?p(void 0):s(new Error(a(t.error.message)))}n(k,"assertAssemblyEngineCompat");async function te(e,r,t,o){const u=await e.cdkService.runCdkSynth(r,i=>t.onCdkOutput?.(i,"synth"));if(!u.success){const i=new Error(a(`${o}: ${u.error}`));return t.onError?.(i),s(i)}const d=k(r.assemblyDir??R(r.path,"cdk.out"),t);if(!d.success){const i=new Error(a(d.error.message));return t.onError?.(i),s(i)}return p(void 0)}n(te,"synthOrFail");async function ne(e,r,t,o){t.onCDKBootstrap?.("bootstrapping");const u=await e.cdkService.runCdkBootstrap(r,I(t),void 0,void 0,o);if(!u.success){t.onCDKBootstrap?.("failed");const d=new Error(a(`Bootstrap failed: ${u.error}`));return t.onError?.(d),s(d)}return t.onCDKBootstrap?.("complete"),p(void 0)}n(ne,"bootstrapOrFail");export{k as assertAssemblyEngineCompat,re as assumeCascadeRole,ne as bootstrapOrFail,q as buildCapacityIdentityContext,L as buildCascadeRoleArn,X as buildParamsContext,ee as collectStackOutputs,I as forwardOutput,Z as forwardResourceProgress,Q as reconcileOidcProviderIfCarried,H as resolveAccountBootstrapSkipOidc,x as synthCarriedOidcConnector,te as synthOrFail,j as targetsNonPrimaryRegion};
1
+ var w=Object.defineProperty;var n=(e,r)=>w(e,"name",{value:r,configurable:!0});import{join as R}from"path";import{success as p,failure as u}from"@fjall/generator";import{getErrorMessage as A,maskSensitiveOutput as a,projectCapacityIdentityToTarget as S,sleep as E}from"@fjall/util";import{logger as T}from"@fjall/util/logger";import{IAMClient as b}from"@aws-sdk/client-iam";import{hasOrganisationTierAccount as h}from"../aws/organisations/accountGlobals.js";import{reconcileOidcProviderAfterDeploy as _}from"../aws/organisations/oidcProvider.js";import{SimpleAwsProvider as $}from"../aws/SimpleAwsProvider.js";import{assertEngineCompatibleWithAssembly as v}from"./application/engineCompat.js";const K={account:"active",draining:"draining",org:"removed"};function x(e,r){return e!==void 0&&e!==""&&r!==void 0&&r!==""&&e!==r}n(x,"targetsNonPrimaryRegion");function q(e){const r=x(e.region,e.primaryRegion);return{...e.orgConfig!==void 0?{orgConfig:JSON.stringify(e.orgConfig)}:{},...e.identity!==void 0?{fjallOrgId:e.identity.fjallOrgId}:{},...e.skipOidc?{fjallOidcConfigured:!0}:{},...r?{fjallAccountGlobalsConfigured:!0}:{},...e.trailLifecycle!==void 0?{fjallAccountTrailState:K[e.trailLifecycle]}:{}}}n(q,"buildParamsContext");function H(e){if(e.capacityIdentity===void 0||e.deployTargetName===void 0)return{};const r=S(e.capacityIdentity,e.deployTargetName);return r!==void 0?{capacityIdentity:JSON.stringify(r)}:{}}n(H,"buildCapacityIdentityContext");function Q(e,r,t,o){if(t!==void 0)return t;if(e==="account")return h(r?.providerAccounts)?o===!0:!0}n(Q,"resolveAccountBootstrapSkipOidc");function j(e){return e.fjallOrgId!==void 0&&e.fjallOrgId!==""&&e.fjallOidcConfigured!==!0&&e.fjallAccountGlobalsConfigured!==!0}n(j,"synthCarriedOidcConnector");async function Z(e,r,t,o){j(e)&&await _(r.getClient(b),t,o)}n(Z,"reconcileOidcProviderIfCarried");function I(e){return r=>e.onOutput?.(r)}n(I,"forwardOutput");function ee(e){return r=>e.onResourceProgress?.(r)}n(ee,"forwardResourceProgress");function M(e,r){for(const t of r?.warnings??[])e.onProgress?.({type:"warning",message:t})}n(M,"emitStepWarnings");function re(e){if(!e.success||e.data.length===0)return;const r={};for(const t of e.data)t.OutputKey&&t.OutputValue!==void 0&&(r[t.OutputKey]=t.OutputValue);return Object.keys(r).length>0?r:void 0}n(re,"collectStackOutputs");const C="OrganizationAccountAccessRole",m=5,P=5e3,D=3e4;function L(e){return`arn:aws:iam::${e}:role/${C}`}n(L,"buildCascadeRoleArn");function k(e,r){return r===void 0?E(e):r.aborted?Promise.resolve():new Promise(t=>{const o=n(()=>{t()},"onAbort");r.addEventListener("abort",o,{once:!0}),E(e).then(()=>{r.removeEventListener("abort",o),t()})})}n(k,"sleepAbortable");async function te(e,r,t,o,i){if(!e.assumeRole)return u(new Error("AwsProvider does not support assumeRole"));const d=L(r),s=e.assumeRole.bind(e);let c;for(let f=0;f<=m;f++)try{c=await s(d,o);break}catch(l){const y=l instanceof Error?l.name:void 0;if(y==="AccessDenied"||y==="AccessDeniedException")return u(new Error(`Access denied assuming ${C} in account ${r}. The role may not exist or may not trust the management account.`));if(f<m){const g=Math.min(P*2**f,D);if(T.debug("assumeCascadeRole",`Attempt ${f+1} failed for account ${r}, retrying in ${Math.round(g/1e3)}s`,{error:a(A(l))}),await k(g,i),i?.aborted)return u(new Error(`Aborted while retrying assume-role for account ${r}`));continue}return u(new Error(`Failed to assume role in account ${r} after ${m+1} attempts: ${a(A(l))}`))}if(!c)return u(new Error(`Failed to assume role in account ${r}`));const O=new $({accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,sessionToken:c.sessionToken,region:t,accountId:r});return p({provider:O,credentials:{accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,sessionToken:c.sessionToken}})}n(te,"assumeCascadeRole");function B(e,r){const t=v({cdkOutPath:e,onWarning:n(o=>r.onLog?.(o,"warn"),"onWarning")});return t.success?p(void 0):u(new Error(a(t.error.message)))}n(B,"assertAssemblyEngineCompat");async function ne(e,r,t,o){const i=await e.cdkService.runCdkSynth(r,s=>t.onCdkOutput?.(s,"synth"));if(!i.success){const s=new Error(a(`${o}: ${i.error}`));return t.onError?.(s),u(s)}M(t,i.data);const d=B(r.assemblyDir??R(r.path,"cdk.out"),t);if(!d.success){const s=new Error(a(d.error.message));return t.onError?.(s),u(s)}return p(void 0)}n(ne,"synthOrFail");async function oe(e,r,t,o){t.onCDKBootstrap?.("bootstrapping");const i=await e.cdkService.runCdkBootstrap(r,I(t),void 0,void 0,o);if(!i.success){t.onCDKBootstrap?.("failed");const d=new Error(a(`Bootstrap failed: ${i.error}`));return t.onError?.(d),u(d)}return t.onCDKBootstrap?.("complete"),p(void 0)}n(oe,"bootstrapOrFail");export{B as assertAssemblyEngineCompat,te as assumeCascadeRole,oe as bootstrapOrFail,H as buildCapacityIdentityContext,L as buildCascadeRoleArn,q as buildParamsContext,re as collectStackOutputs,M as emitStepWarnings,I as forwardOutput,ee as forwardResourceProgress,Z as reconcileOidcProviderIfCarried,Q as resolveAccountBootstrapSkipOidc,j as synthCarriedOidcConnector,ne as synthOrFail,x as targetsNonPrimaryRegion};
@@ -1 +1 @@
1
- var _=Object.defineProperty;var C=(t,n)=>_(t,"name",{value:n,configurable:!0});import{join as x}from"path";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as i,getErrorMessage as k,resolveSynthEnvironment as L}from"@fjall/util";import{stubCallerIdentity as M}from"../../types/deployment/index.js";import{CloudFormationClient as T,DescribeStacksCommand as K}from"@aws-sdk/client-cloudformation";import{S3Client as A}from"@aws-sdk/client-s3";import{BackupClient as V}from"@aws-sdk/client-backup";import{IAMClient as j}from"@aws-sdk/client-iam";import{accountHasDisasterRecovery as U,describeSurvivingBackupVault as G,formatSurvivingVaultWarning as H}from"../../aws/organisations/backup.js";import{cleanupOrphanedOidcProvider as W}from"../../aws/organisations/oidcProvider.js";import{composeSdkAbortSignal as Y}from"../../aws/organisations/types.js";import{assertLeaseBeforeStack as q}from"../application/leaseGate.js";import{ORGANISATION_TYPES as b,getOrganisationStackName as z}from"../../types/operations.js";import{CdkContextBuilder as J}from"../../services/supporting/CdkContextBuilder.js";import{regionSuffix as Q}from"../../types/config/FjallState.js";import{assertAssemblyEngineCompat as X,buildParamsContext as Z,assumeCascadeRole as ee,forwardOutput as B}from"../contextHelpers.js";import{cascadeOperationKey as te}from"./cascadeHelpers.js";import{capturedSweepBuckets as re,cleanupFailedStack as ae,preEmptyStackBuckets as oe,sweepOrphanedDestroyBuckets as I}from"../stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as ne,STACK_FAILED_STATE_PATTERN as se}from"../../types/constants.js";async function Oe(t,n,c,e,r,a,o,y){const g=Date.now(),m=te(e.name,a),f=C((s,p=s)=>(o.onCascadeAccountComplete?.(m,!1,p,a),{accountName:e.name,accountId:e.id,region:a,success:!1,duration:Date.now()-g,error:s}),"fail");o.onCascadeAccountStart?.(m,e.id,a,r);const v=await ee(n.awsProvider,e.id,a,`fjall-cascade-destroy-${e.name}`);if(!v.success){const s=i(v.error.message);return f(`AssumeRole failed: ${s}`,s)}const{provider:u,credentials:D}=v.data,h=x(c.path,`cdk.out.${e.id}.${Q(a)}`),R=J.buildDeploymentContext({deployType:r,target:c.target,path:c.path,assemblyDir:h,region:a,accountName:e.name,environment:L(e),callerIdentity:M(e.id),...Z({orgConfig:t.orgConfig,identity:t.identity,region:a,primaryRegion:y?.primaryRegion})},{verbose:t.options?.verbose},t.orgConfig);o.onCascadeAccountPhaseChange?.(m,"synth",a);const E=await n.cdkService.runCdkSynth(R,B(o));if(!E.success)return f(i(`Synth failed: ${E.error}`));const P=X(h,o);if(!P.success)return f(P.error.message);o.onCascadeAccountPhaseChange?.(m,"destroy",a);const d=z(r==="platform"?b.PLATFORM:b.ACCOUNT),w=await q(t.assertLeaseForStack,d,t.abortSignal,"destroy");if(w!==void 0)return{...f(w.message),leaseDenied:w};U(e.environment,t.orgConfig?.disasterRecoveryRegion)&&await ie(u.getClient(V),a,o,t.abortSignal);const $=await oe(u.getClient(T),u.getClient(A),d,o,t.abortSignal),S=re($),N=await n.cdkService.runCdkDestroy(R,d,B(o),s=>o.onCascadeAccountResourceProgress?.(m,s,a),u,!0,D);if(!N.success){const s=N.error;if(s.includes(se)){l.warn("cascadeDestroy",`CDK destroy failed on ${d} in failed state, retrying via CloudFormation API`,{region:a,account:e.name});try{await ae(d,a,D,{accountId:e.id,abortSignal:t.abortSignal},o)}catch(F){const O=`cleanupFailedStack threw for ${d}: ${i(k(F))}`;l.warn("cascadeDestroy",O),o.onLog?.(O,"warn")}const p=await ue(d,u.getClient(T),t.abortSignal);return p.deleted?(S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}):p.error?f(i(p.error)):f(i(`Stack ${d} cleanup attempted but stack still exists in ${a}`))}return f(i(s))}return S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),(y?.primaryRegion===void 0||a===y.primaryRegion)&&await W(u.getClient(j),o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}}C(Oe,"destroyCascadeAccount");async function ie(t,n,c,e){try{const r=await G(t,n,e);if(!r.success){l.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:i(r.error.message)});return}if(r.data===null)return;c.onProgress?.({type:"warning",message:H(r.data),metadata:{source:"backup-vault-survival"}}),l.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:r.data.vaultName,recoveryPointCount:r.data.recoveryPointCount,lockPermanent:r.data.lockPermanent})}catch(r){l.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:i(k(r))})}}C(ie,"warnIfBackupVaultSurvives");async function ue(t,n,c){try{const r=(await n.send(new K({StackName:t}),{abortSignal:Y(c)})).Stacks?.[0]?.StackStatus;return!r||r==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${r} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(ne))return{deleted:!0};const r=i(k(e));return l.debug("cascadeDestroy","Stack verification failed",{error:r}),{deleted:!1,error:`Stack verification failed: ${r}`}}}C(ue,"verifyStackDeleted");export{Oe as destroyCascadeAccount};
1
+ var _=Object.defineProperty;var C=(t,n)=>_(t,"name",{value:n,configurable:!0});import{join as x}from"path";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as s,getErrorMessage as A,resolveSynthEnvironment as L}from"@fjall/util";import{stubCallerIdentity as M}from"../../types/deployment/index.js";import{CloudFormationClient as T,DescribeStacksCommand as K}from"@aws-sdk/client-cloudformation";import{S3Client as D}from"@aws-sdk/client-s3";import{BackupClient as V}from"@aws-sdk/client-backup";import{IAMClient as j}from"@aws-sdk/client-iam";import{accountHasDisasterRecovery as U,describeSurvivingBackupVault as W,formatSurvivingVaultWarning as G}from"../../aws/organisations/backup.js";import{cleanupOrphanedOidcProvider as H}from"../../aws/organisations/oidcProvider.js";import{composeSdkAbortSignal as Y}from"../../aws/organisations/types.js";import{assertLeaseBeforeStack as q}from"../application/leaseGate.js";import{ORGANISATION_TYPES as B,getOrganisationStackName as z}from"../../types/operations.js";import{CdkContextBuilder as J}from"../../services/supporting/CdkContextBuilder.js";import{regionSuffix as Q}from"../../types/config/FjallState.js";import{assertAssemblyEngineCompat as X,buildParamsContext as Z,assumeCascadeRole as ee,emitStepWarnings as te,forwardOutput as I}from"../contextHelpers.js";import{cascadeOperationKey as re}from"./cascadeHelpers.js";import{capturedSweepBuckets as ae,cleanupFailedStack as oe,preEmptyStackBuckets as ne,sweepOrphanedDestroyBuckets as $}from"../stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as ie,STACK_FAILED_STATE_PATTERN as se}from"../../types/constants.js";async function Te(t,n,c,e,r,a,o,y){const g=Date.now(),m=re(e.name,a),f=C((i,p=i)=>(o.onCascadeAccountComplete?.(m,!1,p,a),{accountName:e.name,accountId:e.id,region:a,success:!1,duration:Date.now()-g,error:i}),"fail");o.onCascadeAccountStart?.(m,e.id,a,r);const v=await ee(n.awsProvider,e.id,a,`fjall-cascade-destroy-${e.name}`);if(!v.success){const i=s(v.error.message);return f(`AssumeRole failed: ${i}`,i)}const{provider:d,credentials:h}=v.data,R=x(c.path,`cdk.out.${e.id}.${Q(a)}`),E=J.buildDeploymentContext({deployType:r,target:c.target,path:c.path,assemblyDir:R,region:a,accountName:e.name,environment:L(e),callerIdentity:M(e.id),...Z({orgConfig:t.orgConfig,identity:t.identity,region:a,primaryRegion:y?.primaryRegion})},{verbose:t.options?.verbose},t.orgConfig);o.onCascadeAccountPhaseChange?.(m,"synth",a);const w=await n.cdkService.runCdkSynth(E,I(o));if(!w.success)return f(s(`Synth failed: ${w.error}`));te(o,w.data);const P=X(R,o);if(!P.success)return f(P.error.message);o.onCascadeAccountPhaseChange?.(m,"destroy",a);const u=z(r==="platform"?B.PLATFORM:B.ACCOUNT),k=await q(t.assertLeaseForStack,u,t.abortSignal,"destroy");if(k!==void 0)return{...f(k.message),leaseDenied:k};U(e.environment,t.orgConfig?.disasterRecoveryRegion)&&await de(d.getClient(V),a,o,t.abortSignal);const b=await ne(d.getClient(T),d.getClient(D),u,o,t.abortSignal),S=ae(b),N=await n.cdkService.runCdkDestroy(E,u,I(o),i=>o.onCascadeAccountResourceProgress?.(m,i,a),d,!0,h);if(!N.success){const i=N.error;if(i.includes(se)){l.warn("cascadeDestroy",`CDK destroy failed on ${u} in failed state, retrying via CloudFormation API`,{region:a,account:e.name});try{await oe(u,a,h,{accountId:e.id,abortSignal:t.abortSignal},o)}catch(F){const O=`cleanupFailedStack threw for ${u}: ${s(A(F))}`;l.warn("cascadeDestroy",O),o.onLog?.(O,"warn")}const p=await ue(u,d.getClient(T),t.abortSignal);return p.deleted?(S.length>0&&await $(d.getClient(D),S,o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}):p.error?f(s(p.error)):f(s(`Stack ${u} cleanup attempted but stack still exists in ${a}`))}return f(s(i))}return S.length>0&&await $(d.getClient(D),S,o,t.abortSignal),(y?.primaryRegion===void 0||a===y.primaryRegion)&&await H(d.getClient(j),o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}}C(Te,"destroyCascadeAccount");async function de(t,n,c,e){try{const r=await W(t,n,e);if(!r.success){l.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:s(r.error.message)});return}if(r.data===null)return;c.onProgress?.({type:"warning",message:G(r.data),metadata:{source:"backup-vault-survival"}}),l.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:r.data.vaultName,recoveryPointCount:r.data.recoveryPointCount,lockPermanent:r.data.lockPermanent})}catch(r){l.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:s(A(r))})}}C(de,"warnIfBackupVaultSurvives");async function ue(t,n,c){try{const r=(await n.send(new K({StackName:t}),{abortSignal:Y(c)})).Stacks?.[0]?.StackStatus;return!r||r==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${r} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(ie))return{deleted:!0};const r=s(A(e));return l.debug("cascadeDestroy","Stack verification failed",{error:r}),{deleted:!1,error:`Stack verification failed: ${r}`}}}C(ue,"verifyStackDeleted");export{Te as destroyCascadeAccount};
@@ -1 +1 @@
1
- var Y=Object.defineProperty;var m=(e,o)=>Y(e,"name",{value:o,configurable:!0});import{join as H}from"path";import{success as I,failure as w}from"@fjall/generator";import{logger as A}from"@fjall/util/logger";import{maskSensitiveOutput as R,mapSettledWithConcurrency as X,resolveSynthEnvironment as q}from"@fjall/util";import{ORGANISATION_TYPES as P,getOrganisationStackName as V}from"../../types/operations.js";import{CdkContextBuilder as J}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as Q}from"../../types/deployment/index.js";import{CloudFormationService as G}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as Z,regionSuffix as ee}from"../../types/config/FjallState.js";import{BackupClient as te}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as oe,describeBackupVaultExists as ne}from"../../aws/organisations/backup.js";import{assertAssemblyEngineCompat as re,buildParamsContext as ae,collectStackOutputs as U,assumeCascadeRole as E,forwardOutput as N,reconcileOidcProviderIfCarried as _}from"../contextHelpers.js";import{assertLeaseBeforeStack as se}from"../application/leaseGate.js";import{deployDelegatedDomain as ie}from"../domain/delegatedDomainDeploy.js";import{normaliseRecordName as j}from"../domain/zoneClassifier.js";import{runDriftPreFlightGate as ce}from"../drift/preFlightGate.js";import{accountTier as K}from"@fjall/util";import{buildBootstrapTagsIfResolved as de}from"../bootstrapTags.js";import{hasOrganisationTierAccount as ue}from"../../aws/organisations/accountGlobals.js";import{DEFAULT_REGION as W}from"../../aws/utils/regions.js";const fe=4;function Me(e){const o=e.find(n=>K(n)==="platform"),c=e.filter(n=>K(n)==="account");return{platformAccount:o,memberAccounts:c}}m(Me,"partitionAccounts");function le(e){const o=e?.primaryRegion??W,c=e?.secondaryRegions??[],n=new Set([o,...c]),i=e?.disasterRecoveryRegion;return i&&n.add(i),[...n]}m(le,"buildRegionList");function Be(e){return le(e)[0]??W}m(Be,"cascadeHomeRegion");function pe(e,o){return`${e} (${o})`}m(pe,"cascadeOperationKey");function Te(e,o){const c=[];for(const n of o)for(const i of e)c.push({account:i,region:n});return c}m(Te,"buildAccountRegionPairs");import{buildCascadeRoleArn as We}from"../contextHelpers.js";async function He(e,o,c,n){c.onLog?.(`Verifying cascade role access for ${o.length} account(s)\u2026`,"info");const i=await X(o,fe,async a=>E(e.awsProvider,a.id,a.region??e.awsProvider.getRegion(),`fjall-preflight-${a.name}`,n)),t=[];return i.forEach((a,s)=>{const r=o[s];if(r){if(a.status==="rejected"){t.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||t.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),t.length===0&&c.onLog?.(`Cascade role access verified for ${o.length} account(s)`,"info"),t}m(He,"probeCascadeRoles");async function Ve(e,o,c,n,i,t,a){const s=a?.region??n.region??o.awsProvider.getRegion(),r=pe(n.name,s),u=a?.orgConfig??e.orgConfig,C=a?.ipamPoolId;t.onCascadeAccountStart?.(r,n.id,s,i);const p=await E(o.awsProvider,n.id,s,`fjall-cascade-${n.name}`,e.abortSignal);if(!p.success)return t.onCascadeAccountComplete?.(r,!1,R(p.error.message),s),w(new Error(`Failed to assume role for ${n.name}: ${R(p.error.message)}`));const{provider:d,credentials:l}=p.data,h=H(e.workingDirectory,"fjall",i==="platform"?P.PLATFORM:P.ACCOUNT),f=H(h,`cdk.out.${n.id}.${ee(s)}`),S=J.buildDeploymentContext({deployType:i,target:c.target,path:h,assemblyDir:f,environment:q(n),region:s,accountName:n.name,callerIdentity:Q(n.id),ipamPoolId:C,...ae({orgConfig:u,identity:e.identity,skipOidc:e.options?.skipOidc,region:s,primaryRegion:a?.primaryRegion,trailLifecycle:n.trailLifecycle})},{verbose:e.options?.verbose},u);if(oe(n.environment,u?.disasterRecoveryRegion)){const g=await ne(d.getClient(te),e.abortSignal);if(!g.success)return t.onCascadeAccountComplete?.(r,!1,R(g.error.message),s),w(new Error(`Backup vault probe failed for ${n.name}: ${R(g.error.message)}`));S.fjallAdoptBackupVault=g.data}t.onCascadeAccountPhaseChange?.(r,"synth",s);const $=await o.cdkService.runCdkSynth(S,N(t),l);if(!$.success)return t.onCascadeAccountComplete?.(r,!1,R(`Synth failed: ${$.error}`),s),w(new Error(`Synth failed for ${n.name}: ${R($.error)}`));const b=re(f,t);if(!b.success){const g=R(b.error.message);return t.onCascadeAccountComplete?.(r,!1,g,s),w(new Error(`Engine compatibility check failed for ${n.name}: ${g}`))}const y=V(i==="platform"?P.PLATFORM:P.ACCOUNT),F=Z(h,n.id,s),D=new G(d),{changed:z,currentHash:L}=await o.hashService.compareCascadeStack(f,y,F);if(!z&&e.options?.force!==!0&&await D.stackExists(y,void 0,e.abortSignal)){const g=await D.getStackOutputs(y,void 0,e.abortSignal);g.success||A.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:y,account:n.name});const T=U(g);return await _(S,d,t,e.abortSignal),t.onLog?.(`${n.name}: no infrastructure changes \u2014 skipping deploy`,"info"),t.onCascadeAccountComplete?.(r,!0,void 0,s,T,!0),I({outputs:T,skipped:!0})}const v=await ce(d,{stackNames:[y],cdkOutPath:f,cfnService:D,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(v.kind==="blocked")return t.onCascadeAccountComplete?.(r,!1,v.message,s),w(new Error(v.message));const O=await se(e.assertLeaseForStack,y,e.abortSignal);if(O!==void 0)return t.onCascadeAccountComplete?.(r,!1,O.message,s),w(O);t.onCascadeAccountPhaseChange?.(r,"bootstrap",s);const x=await o.cdkService.runCdkBootstrap(S,N(t),l,void 0,de({account:n,estate:ue(u?.providerAccounts)?"org":"solo",...a?.orgId!==void 0?{orgId:a.orgId}:{}}));if(!x.success)return t.onCascadeAccountComplete?.(r,!1,R(`Bootstrap failed: ${x.error}`),s),w(new Error(`Bootstrap failed for ${n.name}: ${R(x.error)}`));t.onCascadeAccountPhaseChange?.(r,"deploy",s);const k=await o.cdkService.runCdkDeploy(S,y,N(t),g=>t.onCascadeAccountResourceProgress?.(r,g,s),d,l);if(!k.success)return t.onCascadeAccountComplete?.(r,!1,R(k.error),s),w(new Error(R(k.error)));const M=await D.getStackOutputs(y,void 0,e.abortSignal);M.success||A.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:y,account:n.name});const B=U(M);return await _(S,d,t,e.abortSignal),L!==void 0&&((await o.hashService.persistCascadeStack(F,y,L)).success||A.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:y,account:n.name})),t.onCascadeAccountComplete?.(r,!0,void 0,s,B,!1),I({outputs:B,skipped:!1})}m(Ve,"deployCascadeAccount");async function Ge(e,o,c,n){const i=new Map,t=e.awsProvider.getRegion(),a=await E(e.awsProvider,o.id,t,`fjall-ipam-read-${o.name}`,n);if(!a.success)return A.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const s=new G(a.data.provider),r=V(P.PLATFORM),u=await s.getStackOutputs(r);if(!u.success)return A.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const C=/^IpamPoolId(\d{12})(\w+)$/;for(const p of u.data){const d=p.OutputKey?.match(C);if(d&&p.OutputValue){const l=`${d[1]}-${d[2]}`;i.set(l,p.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}m(Ge,"readPlatformIpamPoolIds");function ge(e,o){const c=e.account!==void 0&&o?.executingAccountId!==void 0&&e.account!==o.executingAccountId,n=e.region!==void 0&&o?.executingRegion!==void 0&&e.region!==o.executingRegion;if(c)return`[${e.name}] skipped: its fjall-config.json entry records account ${e.account} as its home, but this cascade runs in account ${o?.executingAccountId} - deploy it separately with 'fjall domain deploy ${e.name} -t <target in account ${e.account}>' or run the cascade under that account.`;if(n)return`[${e.name}] skipped: its fjall-config.json entry records region ${e.region} as its home, but this cascade runs in region ${o?.executingRegion} - deploy it separately with 'fjall domain deploy ${e.name} --region ${e.region}', or update the entry's "region" key if this move is intentional.`}m(ge,"foreignDomainSkipReason");function me(e){const o=new Map;for(const t of e)o.set(j(t.name),t);const c=new Map,n=m((t,a)=>{const s=c.get(t);if(s!==void 0)return s;if(a.includes(t))return[...a.slice(a.indexOf(t)),t];const r=o.get(t);if(r===void 0)return-1;const u=r.parentDomain!==void 0?j(r.parentDomain):void 0,C=u===void 0?-1:n(u,[...a,t]);if(Array.isArray(C))return C;const p=C+1;return c.set(t,p),p},"resolveDepth"),i=[];for(const t of e){const a=n(j(t.name),[]);if(Array.isArray(a)){const s=a.map(r=>o.get(r)?.name??r).join(" -> ");return w(new Error(`Delegated domain ordering: fjall-config.json declares a parentDomain cycle (${s}) - no deploy order satisfies it. Nothing was deployed. Cure: correct the "parentDomain" of one entry in that cycle so parents form a chain, then re-run.`))}(i[a]??=[]).push(t)}return I(i.filter(t=>t.length>0))}m(me,"planDelegatedDomainWaves");async function Ue(e,o,c){const n=e.getDomains();if(n.length===0)return{domainsDeployed:0,domainsSkipped:0,errors:[]};o.onCascadePhaseStart?.("domains");const i=[];let t=0;for(const d of n){const l=ge(d,c);if(l!==void 0){t++,o.onProgress?.({type:"warning",message:l});continue}i.push(d)}const a=i.filter(d=>d.type==="apex"),s=i.filter(d=>d.type==="delegated");let r=0;const u=[],C=me(s);if(!C.success)return u.push(C.error.message),o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u};const p=C.data;for(const d of a){const l=await e.deployDomain(d.name,o);l.success?r++:u.push(`${d.name}: ${l.error.message}`)}for(const d of p){const l=await Promise.allSettled(d.map(h=>{const f=e.delegatedDeployer?.(h.name);return f!==void 0?ie({deployer:f,callbacks:o,...c?.abortSignal!==void 0?{abortSignal:c.abortSignal}:{},...c?.skipCaaPreflight!==void 0?{skipCaaPreflight:c.skipCaaPreflight}:{}}):e.deployDomain(h.name,o)}));for(let h=0;h<l.length;h++){const f=l[h],S=d[h];if(!(!f||!S))if(f.status==="fulfilled")f.value.success?r++:u.push(`${S.name}: ${f.value.error.message}`);else{const $=f.reason instanceof Error?f.reason.message:String(f.reason);u.push(`${S.name}: ${$}`)}}}return o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u}}m(Ue,"deployDomains");export{fe as CASCADE_MAX_CONCURRENCY,Te as buildAccountRegionPairs,We as buildCascadeRoleArn,le as buildRegionList,Be as cascadeHomeRegion,pe as cascadeOperationKey,Ve as deployCascadeAccount,Ue as deployDomains,Me as partitionAccounts,me as planDelegatedDomainWaves,He as probeCascadeRoles,Ge as readPlatformIpamPoolIds};
1
+ var Y=Object.defineProperty;var m=(e,o)=>Y(e,"name",{value:o,configurable:!0});import{join as H}from"path";import{success as I,failure as w}from"@fjall/generator";import{logger as A}from"@fjall/util/logger";import{maskSensitiveOutput as S,mapSettledWithConcurrency as X,resolveSynthEnvironment as q}from"@fjall/util";import{ORGANISATION_TYPES as P,getOrganisationStackName as V}from"../../types/operations.js";import{CdkContextBuilder as J}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as Q}from"../../types/deployment/index.js";import{CloudFormationService as G}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as Z,regionSuffix as ee}from"../../types/config/FjallState.js";import{BackupClient as te}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as oe,describeBackupVaultExists as ne}from"../../aws/organisations/backup.js";import{assertAssemblyEngineCompat as re,buildParamsContext as ae,collectStackOutputs as U,assumeCascadeRole as E,emitStepWarnings as se,forwardOutput as N,reconcileOidcProviderIfCarried as W}from"../contextHelpers.js";import{assertLeaseBeforeStack as ie}from"../application/leaseGate.js";import{deployDelegatedDomain as ce}from"../domain/delegatedDomainDeploy.js";import{normaliseRecordName as j}from"../domain/zoneClassifier.js";import{runDriftPreFlightGate as de}from"../drift/preFlightGate.js";import{accountTier as _}from"@fjall/util";import{buildBootstrapTagsIfResolved as ue}from"../bootstrapTags.js";import{hasOrganisationTierAccount as fe}from"../../aws/organisations/accountGlobals.js";import{DEFAULT_REGION as K}from"../../aws/utils/regions.js";const le=4;function Be(e){const o=e.find(n=>_(n)==="platform"),c=e.filter(n=>_(n)==="account");return{platformAccount:o,memberAccounts:c}}m(Be,"partitionAccounts");function pe(e){const o=e?.primaryRegion??K,c=e?.secondaryRegions??[],n=new Set([o,...c]),i=e?.disasterRecoveryRegion;return i&&n.add(i),[...n]}m(pe,"buildRegionList");function Te(e){return pe(e)[0]??K}m(Te,"cascadeHomeRegion");function ge(e,o){return`${e} (${o})`}m(ge,"cascadeOperationKey");function He(e,o){const c=[];for(const n of o)for(const i of e)c.push({account:i,region:n});return c}m(He,"buildAccountRegionPairs");import{buildCascadeRoleArn as ze}from"../contextHelpers.js";async function Ve(e,o,c,n){c.onLog?.(`Verifying cascade role access for ${o.length} account(s)\u2026`,"info");const i=await X(o,le,async a=>E(e.awsProvider,a.id,a.region??e.awsProvider.getRegion(),`fjall-preflight-${a.name}`,n)),t=[];return i.forEach((a,s)=>{const r=o[s];if(r){if(a.status==="rejected"){t.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||t.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),t.length===0&&c.onLog?.(`Cascade role access verified for ${o.length} account(s)`,"info"),t}m(Ve,"probeCascadeRoles");async function Ge(e,o,c,n,i,t,a){const s=a?.region??n.region??o.awsProvider.getRegion(),r=ge(n.name,s),u=a?.orgConfig??e.orgConfig,C=a?.ipamPoolId;t.onCascadeAccountStart?.(r,n.id,s,i);const p=await E(o.awsProvider,n.id,s,`fjall-cascade-${n.name}`,e.abortSignal);if(!p.success)return t.onCascadeAccountComplete?.(r,!1,S(p.error.message),s),w(new Error(`Failed to assume role for ${n.name}: ${S(p.error.message)}`));const{provider:d,credentials:l}=p.data,h=H(e.workingDirectory,"fjall",i==="platform"?P.PLATFORM:P.ACCOUNT),f=H(h,`cdk.out.${n.id}.${ee(s)}`),R=J.buildDeploymentContext({deployType:i,target:c.target,path:h,assemblyDir:f,environment:q(n),region:s,accountName:n.name,callerIdentity:Q(n.id),ipamPoolId:C,...ae({orgConfig:u,identity:e.identity,skipOidc:e.options?.skipOidc,region:s,primaryRegion:a?.primaryRegion,trailLifecycle:n.trailLifecycle})},{verbose:e.options?.verbose},u);if(oe(n.environment,u?.disasterRecoveryRegion)){const g=await ne(d.getClient(te),e.abortSignal);if(!g.success)return t.onCascadeAccountComplete?.(r,!1,S(g.error.message),s),w(new Error(`Backup vault probe failed for ${n.name}: ${S(g.error.message)}`));R.fjallAdoptBackupVault=g.data}t.onCascadeAccountPhaseChange?.(r,"synth",s);const $=await o.cdkService.runCdkSynth(R,N(t),l);if(!$.success)return t.onCascadeAccountComplete?.(r,!1,S(`Synth failed: ${$.error}`),s),w(new Error(`Synth failed for ${n.name}: ${S($.error)}`));se(t,$.data);const b=re(f,t);if(!b.success){const g=S(b.error.message);return t.onCascadeAccountComplete?.(r,!1,g,s),w(new Error(`Engine compatibility check failed for ${n.name}: ${g}`))}const y=V(i==="platform"?P.PLATFORM:P.ACCOUNT),F=Z(h,n.id,s),D=new G(d),{changed:z,currentHash:L}=await o.hashService.compareCascadeStack(f,y,F);if(!z&&e.options?.force!==!0&&await D.stackExists(y,void 0,e.abortSignal)){const g=await D.getStackOutputs(y,void 0,e.abortSignal);g.success||A.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:y,account:n.name});const T=U(g);return await W(R,d,t,e.abortSignal),t.onLog?.(`${n.name}: no infrastructure changes \u2014 skipping deploy`,"info"),t.onCascadeAccountComplete?.(r,!0,void 0,s,T,!0),I({outputs:T,skipped:!0})}const v=await de(d,{stackNames:[y],cdkOutPath:f,cfnService:D,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(v.kind==="blocked")return t.onCascadeAccountComplete?.(r,!1,v.message,s),w(new Error(v.message));const O=await ie(e.assertLeaseForStack,y,e.abortSignal);if(O!==void 0)return t.onCascadeAccountComplete?.(r,!1,O.message,s),w(O);t.onCascadeAccountPhaseChange?.(r,"bootstrap",s);const x=await o.cdkService.runCdkBootstrap(R,N(t),l,void 0,ue({account:n,estate:fe(u?.providerAccounts)?"org":"solo",...a?.orgId!==void 0?{orgId:a.orgId}:{}}));if(!x.success)return t.onCascadeAccountComplete?.(r,!1,S(`Bootstrap failed: ${x.error}`),s),w(new Error(`Bootstrap failed for ${n.name}: ${S(x.error)}`));t.onCascadeAccountPhaseChange?.(r,"deploy",s);const k=await o.cdkService.runCdkDeploy(R,y,N(t),g=>t.onCascadeAccountResourceProgress?.(r,g,s),d,l);if(!k.success)return t.onCascadeAccountComplete?.(r,!1,S(k.error),s),w(new Error(S(k.error)));const M=await D.getStackOutputs(y,void 0,e.abortSignal);M.success||A.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:y,account:n.name});const B=U(M);return await W(R,d,t,e.abortSignal),L!==void 0&&((await o.hashService.persistCascadeStack(F,y,L)).success||A.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:y,account:n.name})),t.onCascadeAccountComplete?.(r,!0,void 0,s,B,!1),I({outputs:B,skipped:!1})}m(Ge,"deployCascadeAccount");async function Ue(e,o,c,n){const i=new Map,t=e.awsProvider.getRegion(),a=await E(e.awsProvider,o.id,t,`fjall-ipam-read-${o.name}`,n);if(!a.success)return A.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const s=new G(a.data.provider),r=V(P.PLATFORM),u=await s.getStackOutputs(r);if(!u.success)return A.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const C=/^IpamPoolId(\d{12})(\w+)$/;for(const p of u.data){const d=p.OutputKey?.match(C);if(d&&p.OutputValue){const l=`${d[1]}-${d[2]}`;i.set(l,p.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}m(Ue,"readPlatformIpamPoolIds");function me(e,o){const c=e.account!==void 0&&o?.executingAccountId!==void 0&&e.account!==o.executingAccountId,n=e.region!==void 0&&o?.executingRegion!==void 0&&e.region!==o.executingRegion;if(c)return`[${e.name}] skipped: its fjall-config.json entry records account ${e.account} as its home, but this cascade runs in account ${o?.executingAccountId} - deploy it separately with 'fjall domain deploy ${e.name} -t <target in account ${e.account}>' or run the cascade under that account.`;if(n)return`[${e.name}] skipped: its fjall-config.json entry records region ${e.region} as its home, but this cascade runs in region ${o?.executingRegion} - deploy it separately with 'fjall domain deploy ${e.name} --region ${e.region}', or update the entry's "region" key if this move is intentional.`}m(me,"foreignDomainSkipReason");function Ce(e){const o=new Map;for(const t of e)o.set(j(t.name),t);const c=new Map,n=m((t,a)=>{const s=c.get(t);if(s!==void 0)return s;if(a.includes(t))return[...a.slice(a.indexOf(t)),t];const r=o.get(t);if(r===void 0)return-1;const u=r.parentDomain!==void 0?j(r.parentDomain):void 0,C=u===void 0?-1:n(u,[...a,t]);if(Array.isArray(C))return C;const p=C+1;return c.set(t,p),p},"resolveDepth"),i=[];for(const t of e){const a=n(j(t.name),[]);if(Array.isArray(a)){const s=a.map(r=>o.get(r)?.name??r).join(" -> ");return w(new Error(`Delegated domain ordering: fjall-config.json declares a parentDomain cycle (${s}) - no deploy order satisfies it. Nothing was deployed. Cure: correct the "parentDomain" of one entry in that cycle so parents form a chain, then re-run.`))}(i[a]??=[]).push(t)}return I(i.filter(t=>t.length>0))}m(Ce,"planDelegatedDomainWaves");async function We(e,o,c){const n=e.getDomains();if(n.length===0)return{domainsDeployed:0,domainsSkipped:0,errors:[]};o.onCascadePhaseStart?.("domains");const i=[];let t=0;for(const d of n){const l=me(d,c);if(l!==void 0){t++,o.onProgress?.({type:"warning",message:l});continue}i.push(d)}const a=i.filter(d=>d.type==="apex"),s=i.filter(d=>d.type==="delegated");let r=0;const u=[],C=Ce(s);if(!C.success)return u.push(C.error.message),o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u};const p=C.data;for(const d of a){const l=await e.deployDomain(d.name,o);l.success?r++:u.push(`${d.name}: ${l.error.message}`)}for(const d of p){const l=await Promise.allSettled(d.map(h=>{const f=e.delegatedDeployer?.(h.name);return f!==void 0?ce({deployer:f,callbacks:o,...c?.abortSignal!==void 0?{abortSignal:c.abortSignal}:{},...c?.skipCaaPreflight!==void 0?{skipCaaPreflight:c.skipCaaPreflight}:{}}):e.deployDomain(h.name,o)}));for(let h=0;h<l.length;h++){const f=l[h],R=d[h];if(!(!f||!R))if(f.status==="fulfilled")f.value.success?r++:u.push(`${R.name}: ${f.value.error.message}`);else{const $=f.reason instanceof Error?f.reason.message:String(f.reason);u.push(`${R.name}: ${$}`)}}}return o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u}}m(We,"deployDomains");export{le as CASCADE_MAX_CONCURRENCY,He as buildAccountRegionPairs,ze as buildCascadeRoleArn,pe as buildRegionList,Te as cascadeHomeRegion,ge as cascadeOperationKey,Ge as deployCascadeAccount,We as deployDomains,Be as partitionAccounts,Ce as planDelegatedDomainWaves,Ve as probeCascadeRoles,Ue as readPlatformIpamPoolIds};
@@ -1,3 +1,4 @@
1
- var G=Object.defineProperty;var T=(h,e)=>G(h,"name",{value:e,configurable:!0});import{spawn as y}from"child_process";import{existsSync as P}from"fs";import{join as M}from"path";import{createRequire as B}from"module";import{logger as E}from"@fjall/util/logger";import{filterDangerousEnvVars as _,maskSensitiveOutput as g}from"@fjall/util";import{success as $,failure as n}from"@fjall/generator";import{getErrorMessage as H}from"@fjall/util";import{ensureDependencyIntegrity as L}from"./dependencyIntegrityGate.js";function w(){try{const e=B(import.meta.url).resolve("aws-cdk/bin/cdk");return $({command:process.execPath,prefixArgs:[e]})}catch(h){return E.error("CdkService","Failed to resolve the bundled aws-cdk CLI",{error:g(H(h))}),n("Fjall could not locate its bundled aws-cdk CLI. The deploy toolchain looks misconfigured \u2014 reinstall dependencies (npm install) or reinstall the Fjall CLI, then try again.")}}T(w,"getCdkBinaryPath");const N=5e3,R=1800*1e3;class X{static{T(this,"CdkProcessManager")}runningProcesses=new Map;processCounter=0;argBuilder;abortSignal;exitHandler;sigintHandler;sigtermHandler;constructor(e,i){this.argBuilder=e,this.abortSignal=i,this.exitHandler=()=>this.cleanup(),this.sigintHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(130))},this.sigtermHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(143))},process.on("exit",this.exitHandler),process.on("SIGINT",this.sigintHandler),process.on("SIGTERM",this.sigtermHandler)}forceKillProcess(e){e.stdout?.destroy(),e.stderr?.destroy(),e.kill("SIGTERM");const i=setTimeout(()=>{e.exitCode===null&&e.kill("SIGKILL")},N);i.unref(),e.once("exit",()=>clearTimeout(i))}cleanup(){for(const[e,i]of this.runningProcesses)i.killed||(i.stdout?.destroy(),i.stderr?.destroy(),i.kill("SIGTERM"));this.runningProcesses.clear()}dispose(){this.cleanup(),process.removeListener("exit",this.exitHandler),process.removeListener("SIGINT",this.sigintHandler),process.removeListener("SIGTERM",this.sigtermHandler)}async runCdkCommandPassthrough(e,i,t){if(!P(e))return n(`Directory not found: ${e}`);const u=M(e,"cdk.json");if(!P(u))return n(`No CDK project found in ${e}`);const o=await L(e,{onOutput:t?.outputCallback??(a=>{process.stdout.write(a)}),...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});return o.success?new Promise(a=>{const m=w();if(!m.success){a(n(m.error));return}const p=m.data,C=this.argBuilder.buildContextArgs(t?.context),b=[...p.prefixArgs,...i,...C],k=this.argBuilder.buildCdkEnv(t),l=y(p.command,b,{cwd:e,env:k,stdio:"inherit",shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!l.pid){l.on("error",r=>{E.debug("CdkProcess","Spawn error on failed child process",{error:r.message})}),a(n(`Failed to spawn CDK process - no PID. cwd=${e}, args=${b.join(" ")}`));return}const c=`cdk-passthrough-${++this.processCounter}`;this.runningProcesses.set(c,l);let f=!1,d=!1;const I=t?.timeout??R,S=setTimeout(()=>{l.killed||(f=!0,this.forceKillProcess(l))},I);l.on("close",r=>{if(clearTimeout(S),this.runningProcesses.delete(c),!d){if(d=!0,f){a(n("CDK command timed out"));return}r===0||t?.ignoreExitCode?a($({exitCode:r||0})):a(n(`CDK command failed with exit code ${r}`))}}),l.on("error",r=>{clearTimeout(S),this.runningProcesses.delete(c),!(d||f)&&(d=!0,a(n(`Failed to run CDK command: ${r.message}`)))})}):n(o.error)}async runCdkCommand(e,i,t){if(!P(e))return n(`Directory not found: ${e}`);if(!t?.skipProjectCheck){const u=M(e,"cdk.json");if(!P(u))return n(`No CDK project found in ${e}`);const o=await L(e,{...t?.outputCallback!==void 0?{onOutput:t.outputCallback}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});if(!o.success)return n(o.error)}return new Promise(u=>{let o="",a="",m=!1;const p=w();if(!p.success){u(n(p.error));return}const C=p.data,b=this.argBuilder.buildContextArgs(t?.context),k=[...C.prefixArgs,...i,...b],l={...this.argBuilder.buildCdkEnv(t),NO_COLOR:"1",...t?.extraEnv?_(t.extraEnv):{}};E.debug("CdkService","Spawning CDK process",{command:`${C.command} ${k.join(" ")}`,workingDir:e});const c=y(C.command,k,{cwd:e,env:l,stdio:["ignore","pipe","pipe"],shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!c.pid){const r=`Failed to spawn CDK process - no PID. cwd=${e}, args=${k.join(" ")}`;c.on("error",s=>{E.debug("CdkProcess","Deferred spawn error on failed child process",{error:s.message})}),u(n(r));return}const f=`cdk-${++this.processCounter}`;this.runningProcesses.set(f,c);let d=!1;const I=t?.timeout??R,S=setTimeout(()=>{c.killed||(m=!0,this.forceKillProcess(c))},I);c.stdout?.on("data",r=>{const s=r.toString();o+=s,t?.outputCallback&&t.outputCallback(g(s)),t?.cdkOutputLogger?.writeCdkOutput("stdout",g(s))}),c.stderr?.on("data",r=>{const s=r.toString();t?.cdkOutputLogger?.writeCdkOutput("stderr",g(s)),!s.includes("deprecated")&&!s.includes("npm WARN")&&!s.includes("ENOENT")&&!/\(node:\d+\)/.test(s)&&!s.includes("ExperimentalWarning")&&!s.includes("will no longer support")&&(a+=s),t?.errorCallback&&t.errorCallback(g(s))}),c.on("error",r=>{clearTimeout(S),this.runningProcesses.delete(f),!(d||m)&&(d=!0,u(n(H(r))))}),c.on("close",r=>{if(clearTimeout(S),this.runningProcesses.delete(f),d)return;if(d=!0,m){u(n("CDK command timed out"));return}const s=r===0||t?.ignoreExitCode===!0&&r===1,A=o+(a?`
2
- ${a}`:"");if(s){const x=t?.combineOutput?A:o;u($({output:g(x),exitCode:r||0}));return}let K=a;if(o){const x=o.match(/❌.*?Error:(.*)$/m);x&&(K=x[1]?.trim()??"")}const O=K||`CDK command failed with exit code ${r}`,j=o?`${O}
3
- ${o}`:O;u(n(g(j)))})})}}export{X as CdkProcessManager};
1
+ var B=Object.defineProperty;var P=(m,e)=>B(m,"name",{value:e,configurable:!0});import{spawn as O}from"child_process";import{existsSync as E}from"fs";import{join as y}from"path";import{createRequire as _}from"module";import{logger as I}from"@fjall/util/logger";import{filterDangerousEnvVars as N,maskSensitiveOutput as p}from"@fjall/util";import{success as $,failure as n}from"@fjall/generator";import{getErrorMessage as M}from"@fjall/util";import{ensureDependencyIntegrity as H}from"./dependencyIntegrityGate.js";function L(){try{const e=_(import.meta.url).resolve("aws-cdk/bin/cdk");return $({command:process.execPath,prefixArgs:[e]})}catch(m){return I.error("CdkService","Failed to resolve the bundled aws-cdk CLI",{error:p(M(m))}),n("Fjall could not locate its bundled aws-cdk CLI. The deploy toolchain looks misconfigured \u2014 reinstall dependencies (npm install) or reinstall the Fjall CLI, then try again.")}}P(L,"getCdkBinaryPath");const D=5e3,R=1800*1e3;function A(m,e){const i=e??(t=>{process.stderr.write(t)});for(const t of m)i(`[warn] ${t}
2
+ `)}P(A,"emitGateWarnings");class Y{static{P(this,"CdkProcessManager")}runningProcesses=new Map;processCounter=0;argBuilder;abortSignal;exitHandler;sigintHandler;sigtermHandler;constructor(e,i){this.argBuilder=e,this.abortSignal=i,this.exitHandler=()=>this.cleanup(),this.sigintHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(130))},this.sigtermHandler=()=>{this.abortSignal===void 0&&(this.cleanup(),process.exit(143))},process.on("exit",this.exitHandler),process.on("SIGINT",this.sigintHandler),process.on("SIGTERM",this.sigtermHandler)}forceKillProcess(e){e.stdout?.destroy(),e.stderr?.destroy(),e.kill("SIGTERM");const i=setTimeout(()=>{e.exitCode===null&&e.kill("SIGKILL")},D);i.unref(),e.once("exit",()=>clearTimeout(i))}cleanup(){for(const[e,i]of this.runningProcesses)i.killed||(i.stdout?.destroy(),i.stderr?.destroy(),i.kill("SIGTERM"));this.runningProcesses.clear()}dispose(){this.cleanup(),process.removeListener("exit",this.exitHandler),process.removeListener("SIGINT",this.sigintHandler),process.removeListener("SIGTERM",this.sigtermHandler)}async runCdkCommandPassthrough(e,i,t){if(!E(e))return n(`Directory not found: ${e}`);const u=y(e,"cdk.json");if(!E(u))return n(`No CDK project found in ${e}`);const o=await H(e,{onOutput:t?.outputCallback??(a=>{process.stdout.write(a)}),...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});return o.success?(A(o.data.warnings,t?.outputCallback),new Promise(a=>{const f=L();if(!f.success){a(n(f.error));return}const h=f.data,C=this.argBuilder.buildContextArgs(t?.context),S=[...h.prefixArgs,...i,...C],k=this.argBuilder.buildCdkEnv(t),l=O(h.command,S,{cwd:e,env:k,stdio:"inherit",shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!l.pid){l.on("error",r=>{I.debug("CdkProcess","Spawn error on failed child process",{error:r.message})}),a(n(`Failed to spawn CDK process - no PID. cwd=${e}, args=${S.join(" ")}`));return}const c=`cdk-passthrough-${++this.processCounter}`;this.runningProcesses.set(c,l);let g=!1,d=!1;const T=t?.timeout??R,b=setTimeout(()=>{l.killed||(g=!0,this.forceKillProcess(l))},T);l.on("close",r=>{if(clearTimeout(b),this.runningProcesses.delete(c),!d){if(d=!0,g){a(n("CDK command timed out"));return}r===0||t?.ignoreExitCode?a($({exitCode:r||0})):a(n(`CDK command failed with exit code ${r}`))}}),l.on("error",r=>{clearTimeout(b),this.runningProcesses.delete(c),!(d||g)&&(d=!0,a(n(`Failed to run CDK command: ${r.message}`)))})})):n(o.error)}async runCdkCommand(e,i,t){if(!E(e))return n(`Directory not found: ${e}`);if(!t?.skipProjectCheck){const u=y(e,"cdk.json");if(!E(u))return n(`No CDK project found in ${e}`);const o=await H(e,{...t?.outputCallback!==void 0?{onOutput:t.outputCallback}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});if(!o.success)return n(o.error);A(o.data.warnings,t?.outputCallback)}return new Promise(u=>{let o="",a="",f=!1;const h=L();if(!h.success){u(n(h.error));return}const C=h.data,S=this.argBuilder.buildContextArgs(t?.context),k=[...C.prefixArgs,...i,...S],l={...this.argBuilder.buildCdkEnv(t),NO_COLOR:"1",...t?.extraEnv?N(t.extraEnv):{}};I.debug("CdkService","Spawning CDK process",{command:`${C.command} ${k.join(" ")}`,workingDir:e});const c=O(C.command,k,{cwd:e,env:l,stdio:["ignore","pipe","pipe"],shell:!1,detached:!1,...this.abortSignal!==void 0?{signal:this.abortSignal,killSignal:"SIGTERM"}:{}});if(!c.pid){const r=`Failed to spawn CDK process - no PID. cwd=${e}, args=${k.join(" ")}`;c.on("error",s=>{I.debug("CdkProcess","Deferred spawn error on failed child process",{error:s.message})}),u(n(r));return}const g=`cdk-${++this.processCounter}`;this.runningProcesses.set(g,c);let d=!1;const T=t?.timeout??R,b=setTimeout(()=>{c.killed||(f=!0,this.forceKillProcess(c))},T);c.stdout?.on("data",r=>{const s=r.toString();o+=s,t?.outputCallback&&t.outputCallback(p(s)),t?.cdkOutputLogger?.writeCdkOutput("stdout",p(s))}),c.stderr?.on("data",r=>{const s=r.toString();t?.cdkOutputLogger?.writeCdkOutput("stderr",p(s)),!s.includes("deprecated")&&!s.includes("npm WARN")&&!s.includes("ENOENT")&&!/\(node:\d+\)/.test(s)&&!s.includes("ExperimentalWarning")&&!s.includes("will no longer support")&&(a+=s),t?.errorCallback&&t.errorCallback(p(s))}),c.on("error",r=>{clearTimeout(b),this.runningProcesses.delete(g),!(d||f)&&(d=!0,u(n(M(r))))}),c.on("close",r=>{if(clearTimeout(b),this.runningProcesses.delete(g),d)return;if(d=!0,f){u(n("CDK command timed out"));return}const s=r===0||t?.ignoreExitCode===!0&&r===1,G=o+(a?`
3
+ ${a}`:"");if(s){const x=t?.combineOutput?G:o;u($({output:p(x),exitCode:r||0}));return}let w=a;if(o){const x=o.match(/❌.*?Error:(.*)$/m);x&&(w=x[1]?.trim()??"")}const K=w||`CDK command failed with exit code ${r}`,j=o?`${K}
4
+ ${o}`:K;u(n(p(j)))})})}}export{Y as CdkProcessManager};
@@ -1,16 +1,16 @@
1
- var V=Object.defineProperty;var P=(O,t)=>V(O,"name",{value:t,configurable:!0});import{existsSync as Z}from"fs";import{join as G}from"path";import{logger as I}from"@fjall/util/logger";import{isSuccessState as U,isTerminalState as X}from"../../aws/utils/cloudformationEventHelpers.js";import{success as b,failure as n}from"@fjall/generator";import{DEFAULT_REGION as w}from"../../aws/utils/regions.js";import{getErrorMessage as T,maskSensitiveOutput as y}from"@fjall/util";import{CdkEventMonitor as x,resolveDeployTimeoutMs as tt,settleMonitoringAnalysis as q,startStackMonitoring as et}from"./CdkEventMonitoring.js";import{analyseDeployOutput as rt,analyseDestroyResult as ot,createEnhancedOutputCallback as nt,formatAnalysedDeployFailure as _}from"./CdkOutputAnalyser.js";import{recordDriftSuspects as at}from"../../orchestration/drift/driftSuspectJournal.js";import{CdkCommandRunner as it}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as st}from"./CdkArgumentBuilder.js";import{CdkProcessManager as ct}from"./CdkProcessManager.js";import{cancelInFlightStack as H}from"./cancelInFlightStack.js";import{awaitToolkitSettled as Y,filterBootstrapTagEntries as lt,probeToolkitStack as ut,resolveBootstrapForce as dt,MAX_BOOTSTRAP_LOST_RACE_RETRIES as z,TOOLKIT_SETTLE_BUDGET_MS as mt}from"./bootstrapConvergence.js";import{isAborted as k}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as ft}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as pt,resolveStackName as J,getFallbackStackName as gt,buildDeploymentCdkContext as M}from"./cdkServiceHelpers.js";import{ensureDependencyIntegrity as ht}from"./dependencyIntegrityGate.js";import{runInfraTypecheckPreflight as yt}from"./infraTypecheckPreflight.js";import{isCdkProcessTimeout as St,isTransientNetworkFailure as kt,isReattachableInProgressStatus as bt,MAX_TRANSIENT_NETWORK_RETRIES as K,TRANSIENT_RETRY_BACKOFF_MS as Ct}from"./transientNetworkRecovery.js";import{sleepAbortable as Dt}from"../../util/sleepAbortable.js";class Ht{static{P(this,"CdkService")}commandRunner;eventMonitor;abortSignal;constructor(t){const e=t?.processManager??new ct(new st,t?.abortSignal);this.commandRunner=new it(e),this.eventMonitor=new x({eventLogWriterFactory:t?.eventLogWriterFactory,...t?.onFailureAnalysis!==void 0?{onFailureAnalysis:t.onFailureAnalysis}:{}}),this.abortSignal=t?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(t,e,r){return this.commandRunner.checkDifferences(t,e,r)}async deploy(t,e,r){return this.commandRunner.deploy(t,e,r)}async destroy(t,e,r){return this.commandRunner.destroy(t,e,r)}async runImport(t,e,r){return this.commandRunner.runImport(t,e,r)}async synth(t,e){return this.commandRunner.synth(t,e)}async bootstrap(t,e,r){return this.commandRunner.bootstrap(t,e,r)}async runCdkSynth(t,e,r){const i=t.callerIdentity?.Account;try{const s=await ht(t.path,{...e!==void 0?{onOutput:e}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});if(!s.success)return n(s.error);const a=await yt(t.path,{...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});a.status==="failed"&&(e?.(`WARNING: TypeScript errors in ${t.path} \u2014 the synthesised template may be wrong. This check will block deploys in a future release.
2
- ${a.details}
3
- `),I.warn("CdkService","Infrastructure typecheck failed",{path:t.path}));const o=await this.synth(t.path,{outputCallback:e,context:M(t,i,t.region||w),...t.assemblyDir!==void 0?{outputDir:t.assemblyDir}:{},...r!==void 0?{credentials:r}:{}});return o.success?b({message:"CloudFormation template synthesised",details:o.data.output?{synthesisTime:o.data.output}:void 0}):n(o.error||"Failed to synthesise CloudFormation template")}catch(s){return n(`CDK synth failed: ${y(T(s))}`)}}async runCdkBootstrap(t,e,r,i,s){const a=t.callerIdentity?.Account,o=t.region||w;try{if(!a)return n("No AWS account ID available");const u=G(t.path,"node_modules");if(!Z(u))return n(`Dependencies not installed. Please run 'npm install' in ${t.path} before deploying.`);const d=lt(s),l=Object.keys(d).length>0,f={region:o,...r!==void 0?{credentials:r}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}};for(let p=0;p<=z;p++){if(k(this.abortSignal))return n("Bootstrap cancelled before completion");let m=await ut(f);if(m.kind==="in-progress"&&(e?.(`Another deployment is updating the CDK toolkit in ${a}/${o} \u2014 waiting for it to finish...
4
- `),m=await Y(f)),k(this.abortSignal))return n("Bootstrap cancelled before completion");if(m.kind==="in-progress")return n(`The CDKToolkit stack in ${a}/${o} has been ${m.status} for over ${Math.round(mt/6e4)} minutes. Another deployment may be stuck bootstrapping this environment \u2014 wait for it to finish (or cancel it in the CloudFormation console), then retry.`);const h=await this.bootstrap(a,o,{outputCallback:e,credentials:r,bootstrapForce:dt(m,l?d:void 0),...i!==void 0&&i!==""?{permissionsBoundary:i}:{},...l?{bootstrapTags:d}:{}});if(h.success)return b({message:"AWS environment bootstrapped"});if(k(this.abortSignal))return n("Bootstrap cancelled before completion");if(h.error.kind==="lost-race"&&p<z){I.warn("CdkService","Concurrent bootstrap detected \u2014 waiting for it to settle before retrying",{accountId:a,region:o,attempt:p+1}),e?.(`Another deployment is bootstrapping ${a}/${o} \u2014 waiting for it to finish before retrying...
5
- `),await Y(f);continue}return n(h.error.message||"Failed to bootstrap AWS environment")}return n("Failed to bootstrap AWS environment")}catch(u){return n(`CDK bootstrap failed: ${y(T(u))}`)}}async runCdkDiff(t,e){const r=t.callerIdentity?.Account;try{const i=await this.checkDifferences(t.path,void 0,{verbose:t.options?.verbose,outputCallback:e,context:M(t,r,t.region||w)});return i.success?b({message:"Diff check complete",details:{hasDifferences:i.data.hasDifferences,details:i.data.details}}):n(`CDK diff failed: ${i.error.message}`)}catch(i){return n(`CDK diff failed: ${y(T(i))}`)}}async runCdkDeploy(t,e,r,i,s,a,o,u){const d=t.callerIdentity?.Account,l=t.region||w;if(!d)return n("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!s)return n("AwsProvider is required for deployment monitoring.");const f=t.assemblyDir??G(t.path,"cdk.out"),p=ft(f,i);let m=null,h;try{const v=J(e,t),g=v??gt(t),S=await this.eventMonitor.createEventMonitor("deploy",g,l,t,s);m=S,r&&(r(y(`Starting CloudFormation deployment of ${g}...
1
+ var V=Object.defineProperty;var P=(O,t)=>V(O,"name",{value:t,configurable:!0});import{existsSync as Z}from"fs";import{join as G}from"path";import{logger as I}from"@fjall/util/logger";import{isSuccessState as U,isTerminalState as X}from"../../aws/utils/cloudformationEventHelpers.js";import{success as b,failure as n}from"@fjall/generator";import{DEFAULT_REGION as w}from"../../aws/utils/regions.js";import{getErrorMessage as T,maskSensitiveOutput as y}from"@fjall/util";import{CdkEventMonitor as x,resolveDeployTimeoutMs as tt,settleMonitoringAnalysis as q,startStackMonitoring as et}from"./CdkEventMonitoring.js";import{analyseDeployOutput as rt,analyseDestroyResult as ot,createEnhancedOutputCallback as nt,formatAnalysedDeployFailure as _}from"./CdkOutputAnalyser.js";import{recordDriftSuspects as at}from"../../orchestration/drift/driftSuspectJournal.js";import{CdkCommandRunner as it}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as st}from"./CdkArgumentBuilder.js";import{CdkProcessManager as ct}from"./CdkProcessManager.js";import{cancelInFlightStack as H}from"./cancelInFlightStack.js";import{awaitToolkitSettled as Y,filterBootstrapTagEntries as lt,probeToolkitStack as ut,resolveBootstrapForce as dt,MAX_BOOTSTRAP_LOST_RACE_RETRIES as z,TOOLKIT_SETTLE_BUDGET_MS as mt}from"./bootstrapConvergence.js";import{isAborted as k}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as ft}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as pt,resolveStackName as J,getFallbackStackName as gt,buildDeploymentCdkContext as M}from"./cdkServiceHelpers.js";import{ensureDependencyIntegrity as ht}from"./dependencyIntegrityGate.js";import{runInfraTypecheckPreflight as yt}from"./infraTypecheckPreflight.js";import{isCdkProcessTimeout as St,isTransientNetworkFailure as kt,isReattachableInProgressStatus as bt,MAX_TRANSIENT_NETWORK_RETRIES as K,TRANSIENT_RETRY_BACKOFF_MS as Ct}from"./transientNetworkRecovery.js";import{sleepAbortable as Dt}from"../../util/sleepAbortable.js";class Ht{static{P(this,"CdkService")}commandRunner;eventMonitor;abortSignal;constructor(t){const e=t?.processManager??new ct(new st,t?.abortSignal);this.commandRunner=new it(e),this.eventMonitor=new x({eventLogWriterFactory:t?.eventLogWriterFactory,...t?.onFailureAnalysis!==void 0?{onFailureAnalysis:t.onFailureAnalysis}:{}}),this.abortSignal=t?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(t,e,r){return this.commandRunner.checkDifferences(t,e,r)}async deploy(t,e,r){return this.commandRunner.deploy(t,e,r)}async destroy(t,e,r){return this.commandRunner.destroy(t,e,r)}async runImport(t,e,r){return this.commandRunner.runImport(t,e,r)}async synth(t,e){return this.commandRunner.synth(t,e)}async bootstrap(t,e,r){return this.commandRunner.bootstrap(t,e,r)}async runCdkSynth(t,e,r){const s=t.callerIdentity?.Account;try{const a=await ht(t.path,{...e!==void 0?{onOutput:e}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});if(!a.success)return n(a.error);const i=await yt(t.path,{...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});i.status==="failed"&&(e?.(`WARNING: TypeScript errors in ${t.path} \u2014 the synthesised template may be wrong. This check will block deploys in a future release.
2
+ ${i.details}
3
+ `),I.warn("CdkService","Infrastructure typecheck failed",{path:t.path}));const o=await this.synth(t.path,{outputCallback:e,context:M(t,s,t.region||w),...t.assemblyDir!==void 0?{outputDir:t.assemblyDir}:{},...r!==void 0?{credentials:r}:{}});return o.success?b({message:"CloudFormation template synthesised",...a.data.warnings.length>0?{warnings:a.data.warnings}:{},details:o.data.output?{synthesisTime:o.data.output}:void 0}):n(o.error||"Failed to synthesise CloudFormation template")}catch(a){return n(`CDK synth failed: ${y(T(a))}`)}}async runCdkBootstrap(t,e,r,s,a){const i=t.callerIdentity?.Account,o=t.region||w;try{if(!i)return n("No AWS account ID available");const u=G(t.path,"node_modules");if(!Z(u))return n(`Dependencies not installed. Please run 'npm install' in ${t.path} before deploying.`);const d=lt(a),l=Object.keys(d).length>0,f={region:o,...r!==void 0?{credentials:r}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}};for(let p=0;p<=z;p++){if(k(this.abortSignal))return n("Bootstrap cancelled before completion");let m=await ut(f);if(m.kind==="in-progress"&&(e?.(`Another deployment is updating the CDK toolkit in ${i}/${o} \u2014 waiting for it to finish...
4
+ `),m=await Y(f)),k(this.abortSignal))return n("Bootstrap cancelled before completion");if(m.kind==="in-progress")return n(`The CDKToolkit stack in ${i}/${o} has been ${m.status} for over ${Math.round(mt/6e4)} minutes. Another deployment may be stuck bootstrapping this environment \u2014 wait for it to finish (or cancel it in the CloudFormation console), then retry.`);const h=await this.bootstrap(i,o,{outputCallback:e,credentials:r,bootstrapForce:dt(m,l?d:void 0),...s!==void 0&&s!==""?{permissionsBoundary:s}:{},...l?{bootstrapTags:d}:{}});if(h.success)return b({message:"AWS environment bootstrapped"});if(k(this.abortSignal))return n("Bootstrap cancelled before completion");if(h.error.kind==="lost-race"&&p<z){I.warn("CdkService","Concurrent bootstrap detected \u2014 waiting for it to settle before retrying",{accountId:i,region:o,attempt:p+1}),e?.(`Another deployment is bootstrapping ${i}/${o} \u2014 waiting for it to finish before retrying...
5
+ `),await Y(f);continue}return n(h.error.message||"Failed to bootstrap AWS environment")}return n("Failed to bootstrap AWS environment")}catch(u){return n(`CDK bootstrap failed: ${y(T(u))}`)}}async runCdkDiff(t,e){const r=t.callerIdentity?.Account;try{const s=await this.checkDifferences(t.path,void 0,{verbose:t.options?.verbose,outputCallback:e,context:M(t,r,t.region||w)});return s.success?b({message:"Diff check complete",details:{hasDifferences:s.data.hasDifferences,details:s.data.details}}):n(`CDK diff failed: ${s.error.message}`)}catch(s){return n(`CDK diff failed: ${y(T(s))}`)}}async runCdkDeploy(t,e,r,s,a,i,o,u){const d=t.callerIdentity?.Account,l=t.region||w;if(!d)return n("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!a)return n("AwsProvider is required for deployment monitoring.");const f=t.assemblyDir??G(t.path,"cdk.out"),p=ft(f,s);let m=null,h;try{const v=J(e,t),g=v??gt(t),S=await this.eventMonitor.createEventMonitor("deploy",g,l,t,a);m=S,r&&(r(y(`Starting CloudFormation deployment of ${g}...
6
6
  `)),r(`Monitoring CloudFormation events (CDK process running in background)...
7
- `));const c={cdkOutput:"",actualStackName:g,stackNameIsAuthoritative:v!==void 0,stackDetected:!1,monitoringPromise:null},Q=nt(c,r,m,p);h=setTimeout(()=>{!c.stackDetected&&!c.monitoringPromise&&(I.debug("CdkService","Fallback monitoring STARTING",{targetStackName:g,stackDetected:c.stackDetected,hasOnResourceProgress:!!i}),c.monitoringPromise=et(S,g,p))},pt);const L=t.options?.deployTimeoutMs??tt(),W=Date.now();for(let R=0;;R++){const A=await this.deploy(t.path,g,{verbose:t.options?.verbose,timeout:Math.max(1,L-(Date.now()-W)),outputCallback:Q,...t.assemblyDir!==void 0?{appDir:t.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:S.getEventLogger()??void 0,context:M(t,d,l),credentials:a,...o!==void 0&&Object.keys(o).length>0&&{parameters:o},...u===!0&&{exclusively:!0}});if(k(this.abortSignal))return await H(g,l,a,r),n("Deployment cancelled");const $=rt(c.cdkOutput,A,c.actualStackName);if($.success)return await this.reconcileDeploySuccessAgainstStack({successOutput:$.data,stackName:c.actualStackName,monitor:S,monitoringPromise:c.monitoringPromise,accountId:d,region:l,onOutput:r});const B=[c.cdkOutput,A.success?A.data.output??"":A.error,$.error].join(`
7
+ `));const c={cdkOutput:"",actualStackName:g,stackNameIsAuthoritative:v!==void 0,stackDetected:!1,monitoringPromise:null},Q=nt(c,r,m,p);h=setTimeout(()=>{!c.stackDetected&&!c.monitoringPromise&&(I.debug("CdkService","Fallback monitoring STARTING",{targetStackName:g,stackDetected:c.stackDetected,hasOnResourceProgress:!!s}),c.monitoringPromise=et(S,g,p))},pt);const L=t.options?.deployTimeoutMs??tt(),W=Date.now();for(let R=0;;R++){const A=await this.deploy(t.path,g,{verbose:t.options?.verbose,timeout:Math.max(1,L-(Date.now()-W)),outputCallback:Q,...t.assemblyDir!==void 0?{appDir:t.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:S.getEventLogger()??void 0,context:M(t,d,l),credentials:i,...o!==void 0&&Object.keys(o).length>0&&{parameters:o},...u===!0&&{exclusively:!0}});if(k(this.abortSignal))return await H(g,l,i,r),n("Deployment cancelled");const $=rt(c.cdkOutput,A,c.actualStackName);if($.success)return await this.reconcileDeploySuccessAgainstStack({successOutput:$.data,stackName:c.actualStackName,monitor:S,monitoringPromise:c.monitoringPromise,accountId:d,region:l,onOutput:r});const B=[c.cdkOutput,A.success?A.data.output??"":A.error,$.error].join(`
8
8
  `),N=St(B);if(kt(B)||N){r?.(N?`The CDK child for ${c.actualStackName} outlived its timeout and was killed \u2014 that does not stop the CloudFormation operation. Probing for an in-flight operation...
9
9
  `:`Transient network failure while running CDK for ${c.actualStackName} \u2014 probing CloudFormation for an in-flight operation...
10
- `);const C=await S.getStackStatus(c.actualStackName,this.abortSignal);if(C!==null&&bt(C.status))return clearTimeout(h),await this.reattachToInFlightOperation({stackName:c.actualStackName,currentStatus:C.status,region:l,accountId:d,monitor:S,state:c,credentials:a,remainingTimeoutMs:Math.max(0,L-(Date.now()-W)),onOutput:r,onResourceProgress:p});if(N){const D=C?.status;return n(`CDK deploy for ${c.actualStackName} exceeded its timeout`+(D!==void 0?`; CloudFormation reports ${D}`:" and CloudFormation reports no operation in progress")+". Raise the deploy timeout rather than re-running \u2014 the stack was not interrupted by an error.")}const F=C?.status,j=F!==void 0&&X(F)&&!U(F);if(!j&&R<K){const D=Ct[R]??15e3;if(r?.(`No CloudFormation operation in progress \u2014 nothing was mutated. Retrying the CDK deploy in ${D/1e3}s (retry ${R+1} of ${K})...
10
+ `);const C=await S.getStackStatus(c.actualStackName,this.abortSignal);if(C!==null&&bt(C.status))return clearTimeout(h),await this.reattachToInFlightOperation({stackName:c.actualStackName,currentStatus:C.status,region:l,accountId:d,monitor:S,state:c,credentials:i,remainingTimeoutMs:Math.max(0,L-(Date.now()-W)),onOutput:r,onResourceProgress:p});if(N){const D=C?.status;return n(`CDK deploy for ${c.actualStackName} exceeded its timeout`+(D!==void 0?`; CloudFormation reports ${D}`:" and CloudFormation reports no operation in progress")+". Raise the deploy timeout rather than re-running \u2014 the stack was not interrupted by an error.")}const F=C?.status,j=F!==void 0&&X(F)&&!U(F);if(!j&&R<K){const D=Ct[R]??15e3;if(r?.(`No CloudFormation operation in progress \u2014 nothing was mutated. Retrying the CDK deploy in ${D/1e3}s (retry ${R+1} of ${K})...
11
11
  `),await Dt(D,this.abortSignal),k(this.abortSignal))return n("Deployment cancelled");c.cdkOutput="";continue}r?.(j?`CloudFormation settled at ${F} for ${c.actualStackName} \u2014 the stack was rolled back, not cleanly interrupted. Not retrying.
12
12
  `:`No CloudFormation operation in progress and the retry budget (${K}) is exhausted \u2014 giving up.
13
- `)}const E=await q(S,c.monitoringPromise);return E===null?$:(await this.journalDriftSuspects(d,l,c.actualStackName,E),n(_(E)))}}catch(v){const g=`CDK deploy failed: ${y(T(v))}`;return I.error("CdkService","CDK deployment exception",{error:g}),n(g)}finally{clearTimeout(h),m&&m.stopMonitoring()}}async reattachToInFlightOperation(t){const{stackName:e,monitor:r,state:i,onOutput:s}=t;s?.(`CloudFormation reports ${t.currentStatus} for ${e} \u2014 the operation survived the network failure. Re-attaching and monitoring it to completion...
14
- `),r.stopMonitoring(),i.monitoringPromise=null;const a=P(()=>{r.stopMonitoring()},"onAbort");this.abortSignal?.addEventListener("abort",a,{once:!0});const o=await r.waitForStackComplete(e,{timeout:t.remainingTimeoutMs,...t.onResourceProgress!==void 0?{onResourceUpdate:t.onResourceProgress}:{}}).finally(()=>{this.abortSignal?.removeEventListener("abort",a)});if(k(this.abortSignal))return await H(e,t.region,t.credentials,s),n("Deployment cancelled");if(o.success){const l=o.status??"COMPLETE";return s?.(`${e} reached ${l} after re-attach.
15
- `),b({message:`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${l}`,details:{reattached:!0,status:l}})}const u=r.getFailureAnalysis();if(u!==null)return await this.journalDriftSuspects(t.accountId,t.region,e,u),n(_(u));const d=o.failureReason!==void 0&&o.failureReason!==""?y(o.failureReason):"no failure reason reported";return n(`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${o.status??"UNKNOWN"}: ${d}`)}async reconcileDeploySuccessAgainstStack(t){const{successOutput:e,stackName:r,monitor:i,onOutput:s}=t;if(e.status==="NO_CHANGES"||e.status==="SKIPPED"||e.details?.skipped===!0)return b(e);const a=await i.getStackStatus(r,this.abortSignal);if(a===null||!X(a.status)||U(a.status))return b(e);s?.(`CDK reported success but CloudFormation settled at ${a.status} for ${r} \u2014 the deployment did not converge. Reporting failure.
16
- `);const o=await q(i,t.monitoringPromise);if(o!==null)return await this.journalDriftSuspects(t.accountId,t.region,r,o),n(_(o));const u=a.statusReason!==void 0&&a.statusReason!==""?y(a.statusReason):"no failure reason reported";return n(`Deployment of ${r} reported success but CloudFormation settled at ${a.status}: ${u}`)}async journalDriftSuspects(t,e,r,i){i.driftSuspects===void 0||i.driftSuspects.length===0||await at({accountId:t,region:e,stackName:r,recordedAt:new Date().toISOString(),suspects:i.driftSuspects.map(s=>({stackName:r,logicalId:s.logicalId,resourceType:s.resourceType,...s.physicalId!==void 0?{physicalId:s.physicalId}:{},failedStatus:s.failedStatus,statusReason:s.statusReason}))})}async runCdkDestroy(t,e,r,i,s,a,o){const u=t.callerIdentity?.Account,d=t.region||w;let l=null;try{const f=J(e,t);u&&f&&s&&(l=await this.eventMonitor.createEventMonitor("destroy",f,d,t,s),l.startMonitoring(f,m=>{i?.(m)},(m,h)=>{}));const p=await this.destroy(t.path,e,{verbose:t.options?.verbose,outputCallback:r,useCdkOut:a,cdkOutputLogger:l?.getEventLogger()??void 0,context:M(t,u,d),credentials:o});return k(this.abortSignal)?n("Destroy cancelled"):ot(p)}catch(f){return n(`CDK destroy failed: ${y(T(f))}`)}finally{l&&l.stopMonitoring()}}}export{Ht as CdkService};
13
+ `)}const E=await q(S,c.monitoringPromise);return E===null?$:(await this.journalDriftSuspects(d,l,c.actualStackName,E),n(_(E)))}}catch(v){const g=`CDK deploy failed: ${y(T(v))}`;return I.error("CdkService","CDK deployment exception",{error:g}),n(g)}finally{clearTimeout(h),m&&m.stopMonitoring()}}async reattachToInFlightOperation(t){const{stackName:e,monitor:r,state:s,onOutput:a}=t;a?.(`CloudFormation reports ${t.currentStatus} for ${e} \u2014 the operation survived the network failure. Re-attaching and monitoring it to completion...
14
+ `),r.stopMonitoring(),s.monitoringPromise=null;const i=P(()=>{r.stopMonitoring()},"onAbort");this.abortSignal?.addEventListener("abort",i,{once:!0});const o=await r.waitForStackComplete(e,{timeout:t.remainingTimeoutMs,...t.onResourceProgress!==void 0?{onResourceUpdate:t.onResourceProgress}:{}}).finally(()=>{this.abortSignal?.removeEventListener("abort",i)});if(k(this.abortSignal))return await H(e,t.region,t.credentials,a),n("Deployment cancelled");if(o.success){const l=o.status??"COMPLETE";return a?.(`${e} reached ${l} after re-attach.
15
+ `),b({message:`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${l}`,details:{reattached:!0,status:l}})}const u=r.getFailureAnalysis();if(u!==null)return await this.journalDriftSuspects(t.accountId,t.region,e,u),n(_(u));const d=o.failureReason!==void 0&&o.failureReason!==""?y(o.failureReason):"no failure reason reported";return n(`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${o.status??"UNKNOWN"}: ${d}`)}async reconcileDeploySuccessAgainstStack(t){const{successOutput:e,stackName:r,monitor:s,onOutput:a}=t;if(e.status==="NO_CHANGES"||e.status==="SKIPPED"||e.details?.skipped===!0)return b(e);const i=await s.getStackStatus(r,this.abortSignal);if(i===null||!X(i.status)||U(i.status))return b(e);a?.(`CDK reported success but CloudFormation settled at ${i.status} for ${r} \u2014 the deployment did not converge. Reporting failure.
16
+ `);const o=await q(s,t.monitoringPromise);if(o!==null)return await this.journalDriftSuspects(t.accountId,t.region,r,o),n(_(o));const u=i.statusReason!==void 0&&i.statusReason!==""?y(i.statusReason):"no failure reason reported";return n(`Deployment of ${r} reported success but CloudFormation settled at ${i.status}: ${u}`)}async journalDriftSuspects(t,e,r,s){s.driftSuspects===void 0||s.driftSuspects.length===0||await at({accountId:t,region:e,stackName:r,recordedAt:new Date().toISOString(),suspects:s.driftSuspects.map(a=>({stackName:r,logicalId:a.logicalId,resourceType:a.resourceType,...a.physicalId!==void 0?{physicalId:a.physicalId}:{},failedStatus:a.failedStatus,statusReason:a.statusReason}))})}async runCdkDestroy(t,e,r,s,a,i,o){const u=t.callerIdentity?.Account,d=t.region||w;let l=null;try{const f=J(e,t);u&&f&&a&&(l=await this.eventMonitor.createEventMonitor("destroy",f,d,t,a),l.startMonitoring(f,m=>{s?.(m)},(m,h)=>{}));const p=await this.destroy(t.path,e,{verbose:t.options?.verbose,outputCallback:r,useCdkOut:i,cdkOutputLogger:l?.getEventLogger()??void 0,context:M(t,u,d),credentials:o});return k(this.abortSignal)?n("Destroy cancelled"):ot(p)}catch(f){return n(`CDK destroy failed: ${y(T(f))}`)}finally{l&&l.stopMonitoring()}}}export{Ht as CdkService};
@@ -37,6 +37,13 @@ export interface EnsureDependencyIntegrityOptions {
37
37
  export type DependencyIntegrityStatus = "satisfied" | "installed" | "skipped";
38
38
  export interface DependencyIntegrityOutcome {
39
39
  status: DependencyIntegrityStatus;
40
+ /**
41
+ * Pre-masked notes for pin comparisons the verification SKIPPED (installed
42
+ * manifest present but unreadable, or carrying no usable version). Carried
43
+ * in the outcome - not emitted here - so cache-hit re-entries repeat them;
44
+ * each caller owns the rendering for its own surface.
45
+ */
46
+ warnings: string[];
40
47
  }
41
48
  export declare function clearDependencyIntegrityCache(): void;
42
49
  /**
@@ -1,2 +1,2 @@
1
- var E=Object.defineProperty;var i=(t,e)=>E(t,"name",{value:e,configurable:!0});import{existsSync as m,readFileSync as O,realpathSync as T}from"fs";import{dirname as S,join as h,relative as U,resolve as C,sep as I}from"path";import{filterDangerousEnvVars as R,getErrorMessage as F,maskSensitiveOutput as $}from"@fjall/util";import{logger as A}from"@fjall/util/logger";import{success as k,failure as a}from"@fjall/generator";import{spawnWithTimeout as J}from"../../orchestration/spawnHelpers.js";const N=300*1e3,P=1500,w=new Map,b=new Map;function re(){w.clear(),b.clear()}i(re,"clearDependencyIntegrityCache");function H(t){const e=$(t.trim());return e.length>P?`...${e.slice(-P)}`:e}i(H,"maskedTail");function L(t){try{return T(t)}catch{return C(t)}}i(L,"resolveCacheKey");function j(t){try{const e=JSON.parse(O(t,"utf-8"));return e===null||typeof e!="object"||Array.isArray(e)?a("not a JSON object"):k(e)}catch(e){return a(F(e))}}i(j,"readJsonFile");function V(t){const e=new Set;for(const r of["dependencies","devDependencies"]){const o=t[r];if(o!==null&&typeof o=="object"&&!Array.isArray(o))for(const s of Object.keys(o))e.add(s)}const n=t.optionalDependencies;if(n!==null&&typeof n=="object"&&!Array.isArray(n))for(const r of Object.keys(n))e.delete(r);return[...e]}i(V,"collectDeclaredDeps");function W(t,e){const r=t.replace(/^\.\//,"").replace(/\/+$/,"").split("/").map(o=>o==="**"?".*":o.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^/]*")).join("/");return new RegExp(`^${r}$`).test(e)}i(W,"workspacePatternMatches");function Y(t){const e=t.workspaces,n=Array.isArray(e)?e:e!==null&&typeof e=="object"?e.packages:void 0;return Array.isArray(n)?n.filter(r=>typeof r=="string"):[]}i(Y,"workspacePatterns");function K(t){const e=C(t);let n=S(e);for(;;){const r=h(n,"package.json");if(m(r)){const s=j(r);if(s.success){const u=U(n,e).split(I).join("/");if(Y(s.data).some(f=>W(f,u)))return n}}const o=S(n);if(o===n)return;n=o}}i(K,"findWorkspaceRoot");function q(t,e){const n=t.packages,r=n!==null&&typeof n=="object"?n[`node_modules/${e}`]:z(t,e);if(r===null||typeof r!="object")return;const o=r.version;return typeof o=="string"?o:void 0}i(q,"lockfilePin");function z(t,e){const n=t.dependencies;if(!(n===null||typeof n!="object"))return n[e]}i(z,"readV1DependencyEntry");function D(t,e,n,r){const o=[];for(const s of e){let u=h(t,"node_modules",s,"package.json");if(!m(u)){const d=r!==void 0?h(r,"node_modules",s,"package.json"):void 0;if(d===void 0||!m(d)){o.push({name:s,detail:`${s} is not installed locally`});continue}u=d}if(n===void 0)continue;const f=q(n,s);if(f===void 0)continue;const y=j(u);if(!y.success)continue;const p=y.data.version;typeof p=="string"&&p!==f&&o.push({name:s,detail:`${s} is installed at ${p} but package-lock.json pins ${f}`})}return o}i(D,"findUnsatisfiedDeps");function x(t){return t.map(e=>e.detail).join("; ")}i(x,"describeUnsatisfied");async function oe(t,e={}){const n=L(t),r=w.get(n);if(r!==void 0)return k(r);const o=b.get(n);if(o!==void 0)return o;const s=B(n,t,e).finally(()=>{b.delete(n)});return b.set(n,s),s}i(oe,"ensureDependencyIntegrity");async function B(t,e,n){const r=h(e,"package.json");if(!m(r))return a(`Dependency integrity gate: no package.json in ${e}, so its declared dependencies cannot be verified and the synth could silently resolve packages from an ancestor directory. Nothing was synthesised or deployed. Cure: restore package.json in ${e} from version control (or re-scaffold the folder), then retry.`);const o=j(r);if(!o.success)return a(`Dependency integrity gate: package.json in ${e} could not be parsed (${$(o.error)}). Nothing was synthesised or deployed. Cure: fix the JSON in ${r}, then retry.`);const s=V(o.data),u=h(e,"package-lock.json"),f=m(u);let y;if(f){const l=j(u);if(!l.success)return a(`Dependency integrity gate: package-lock.json in ${e} could not be parsed (${$(l.error)}), so npm ci would fail. Nothing was synthesised or deployed. Cure: restore package-lock.json in ${e} from version control, or delete it and run npm install there to regenerate it, then retry.`);y=l.data}let p=D(e,s,y,void 0),d;if(p.length>0&&(d=K(e),d!==void 0&&(p=D(e,s,y,d))),p.length===0){const l={status:"satisfied"};return w.set(t,l),k(l)}const c=f?"ci":"install";n.onOutput?.(`Syncing infrastructure dependencies (npm ${c}) in ${e}...
2
- `),A.info("CdkService","Syncing declaration-folder dependencies",{path:e,subcommand:c,unsatisfied:x(p)});const M=Date.now(),g=await J({command:"npm",args:[c,"--no-audit","--no-fund","--include=dev"],cwd:e,env:R(process.env),timeout:N,...n.abortSignal!==void 0?{abortSignal:n.abortSignal}:{}});switch(g.type){case"success":{if(g.code===0){const l=D(e,s,y,d);if(l.length>0){const _=d!==void 0?`run npm install in ${d} (the npm workspace root that declares ${e})`:`run npm ${c} in ${e}`;return a(`Dependency integrity gate: npm ${c} completed in ${e} but ${x(l)}. Nothing was synthesised or deployed. Cure: ${_} and inspect the reported packages, then retry.`)}A.info("CdkService","Declaration-folder dependencies synced",{path:e,durationMs:Date.now()-M});const v={status:"installed"};return w.set(t,v),k(v)}return a(`Dependency integrity gate: npm ${c} failed (exit ${g.code}) in ${e}. Nothing was synthesised or deployed. Cure: run npm ${c} in ${e} and fix the reported npm error, then retry. npm output: ${H(g.stderr||g.stdout)}`)}case"timeout":return a(`Dependency integrity gate: npm ${c} timed out after ${N}ms in ${e}. Nothing was synthesised or deployed. Cure: run npm ${c} in ${e} to diagnose the slow install (network or registry), then retry.`);case"aborted":return a(`Dependency integrity gate: the dependency sync (npm ${c}) in ${e} was cancelled before it completed. Nothing was synthesised or deployed. Cure: re-run the command to restart the sync.`);case"enoent":return a("npm was not found on PATH. Install Node.js 22+ (which bundles npm) on this machine or CI agent, then retry.");case"spawn_error":return a(`Dependency integrity gate: npm ${c} could not be started in ${e} (${$(g.error.message)}). Nothing was synthesised or deployed. Cure: check that the npm binary on PATH exists and is executable, then retry.`)}}i(B,"verifyUncached");export{N as DEPENDENCY_SYNC_TIMEOUT_MS,re as clearDependencyIntegrityCache,oe as ensureDependencyIntegrity};
1
+ var _=Object.defineProperty;var o=(t,e)=>_(t,"name",{value:e,configurable:!0});import{existsSync as m,readFileSync as E,realpathSync as T}from"fs";import{dirname as S,join as $,relative as I,resolve as C,sep as U}from"path";import{filterDangerousEnvVars as R,getErrorMessage as F,maskSensitiveOutput as h}from"@fjall/util";import{logger as j}from"@fjall/util/logger";import{success as b,failure as d}from"@fjall/generator";import{spawnWithTimeout as J}from"../../orchestration/spawnHelpers.js";const A=300*1e3,N=1500,v=new Map,k=new Map;function re(){v.clear(),k.clear()}o(re,"clearDependencyIntegrityCache");function H(t){const e=h(t.trim());return e.length>N?`...${e.slice(-N)}`:e}o(H,"maskedTail");function L(t){try{return T(t)}catch{return C(t)}}o(L,"resolveCacheKey");function w(t){try{const e=JSON.parse(E(t,"utf-8"));return e===null||typeof e!="object"||Array.isArray(e)?d("not a JSON object"):b(e)}catch(e){return d(F(e))}}o(w,"readJsonFile");function V(t){const e=new Set;for(const r of["dependencies","devDependencies"]){const s=t[r];if(s!==null&&typeof s=="object"&&!Array.isArray(s))for(const i of Object.keys(s))e.add(i)}const n=t.optionalDependencies;if(n!==null&&typeof n=="object"&&!Array.isArray(n))for(const r of Object.keys(n))e.delete(r);return[...e]}o(V,"collectDeclaredDeps");function W(t,e){const r=t.replace(/^\.\//,"").replace(/\/+$/,"").split("/").map(s=>s==="**"?".*":s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^/]*")).join("/");return new RegExp(`^${r}$`).test(e)}o(W,"workspacePatternMatches");function Y(t){const e=t.workspaces,n=Array.isArray(e)?e:e!==null&&typeof e=="object"?e.packages:void 0;return Array.isArray(n)?n.filter(r=>typeof r=="string"):[]}o(Y,"workspacePatterns");function K(t){const e=C(t);let n=S(e);for(;;){const r=$(n,"package.json");if(m(r)){const i=w(r);if(i.success){const a=I(n,e).split(U).join("/");if(Y(i.data).some(l=>W(l,a)))return n}}const s=S(n);if(s===n)return;n=s}}o(K,"findWorkspaceRoot");function q(t,e){const n=t.packages,r=n!==null&&typeof n=="object"?n[`node_modules/${e}`]:z(t,e);if(r===null||typeof r!="object")return;const s=r.version;return typeof s=="string"?s:void 0}o(q,"lockfilePin");function z(t,e){const n=t.dependencies;if(!(n===null||typeof n!="object"))return n[e]}o(z,"readV1DependencyEntry");function D(t,e,n,r){const s=[],i=[];for(const a of e){let l=$(t,"node_modules",a,"package.json");if(!m(l)){const c=r!==void 0?$(r,"node_modules",a,"package.json"):void 0;if(c===void 0||!m(c)){s.push({name:a,detail:`${a} is not installed locally`});continue}l=c}if(n===void 0)continue;const u=q(n,a);if(u===void 0)continue;const p=w(l);if(!p.success){i.push(`${a}: installed package.json at ${l} could not be read (${h(p.error)}), so the package-lock.json pin ${u} was not verified for this package.`);continue}const f=p.data.version;if(typeof f!="string"){i.push(`${a}: installed package.json at ${l} records no usable version, so the package-lock.json pin ${u} was not verified for this package.`);continue}f!==u&&s.push({name:a,detail:`${a} is installed at ${f} but package-lock.json pins ${u}`})}return{unsatisfied:s,warnings:i}}o(D,"findUnsatisfiedDeps");function P(t){return t.map(e=>e.detail).join("; ")}o(P,"describeUnsatisfied");function x(t,e,n,r){r.length>0&&j.warn("CdkService","Dependency pin comparisons skipped",{path:e,warnings:r});const s={status:n,warnings:r};return v.set(t,s),b(s)}o(x,"settleOutcome");async function se(t,e={}){const n=L(t),r=v.get(n);if(r!==void 0)return b(r);const s=k.get(n);if(s!==void 0)return s;const i=B(n,t,e).finally(()=>{k.delete(n)});return k.set(n,i),i}o(se,"ensureDependencyIntegrity");async function B(t,e,n){const r=$(e,"package.json");if(!m(r))return d(`Dependency integrity gate: no package.json in ${e}, so its declared dependencies cannot be verified and the synth could silently resolve packages from an ancestor directory. Nothing was synthesised or deployed. Cure: restore package.json in ${e} from version control (or re-scaffold the folder), then retry.`);const s=w(r);if(!s.success)return d(`Dependency integrity gate: package.json in ${e} could not be parsed (${h(s.error)}). Nothing was synthesised or deployed. Cure: fix the JSON in ${r}, then retry.`);const i=V(s.data),a=$(e,"package-lock.json"),l=m(a);let u;if(l){const y=w(a);if(!y.success)return d(`Dependency integrity gate: package-lock.json in ${e} could not be parsed (${h(y.error)}), so npm ci would fail. Nothing was synthesised or deployed. Cure: restore package-lock.json in ${e} from version control, or delete it and run npm install there to regenerate it, then retry.`);u=y.data}let p=D(e,i,u,void 0),f;if(p.unsatisfied.length>0&&(f=K(e),f!==void 0&&(p=D(e,i,u,f))),p.unsatisfied.length===0)return x(t,e,"satisfied",p.warnings);const c=l?"ci":"install";n.onOutput?.(`Syncing infrastructure dependencies (npm ${c}) in ${e}...
2
+ `),j.info("CdkService","Syncing declaration-folder dependencies",{path:e,subcommand:c,unsatisfied:P(p.unsatisfied)});const M=Date.now(),g=await J({command:"npm",args:[c,"--no-audit","--no-fund","--include=dev"],cwd:e,env:R(process.env),timeout:A,...n.abortSignal!==void 0?{abortSignal:n.abortSignal}:{}});switch(g.type){case"success":{if(g.code===0){const y=D(e,i,u,f);if(y.unsatisfied.length>0){const O=f!==void 0?`run npm install in ${f} (the npm workspace root that declares ${e})`:`run npm ${c} in ${e}`;return d(`Dependency integrity gate: npm ${c} completed in ${e} but ${P(y.unsatisfied)}. Nothing was synthesised or deployed. Cure: ${O} and inspect the reported packages, then retry.`)}return j.info("CdkService","Declaration-folder dependencies synced",{path:e,durationMs:Date.now()-M}),x(t,e,"installed",y.warnings)}return d(`Dependency integrity gate: npm ${c} failed (exit ${g.code}) in ${e}. Nothing was synthesised or deployed. Cure: run npm ${c} in ${e} and fix the reported npm error, then retry. npm output: ${H(g.stderr||g.stdout)}`)}case"timeout":return d(`Dependency integrity gate: npm ${c} timed out after ${A}ms in ${e}. Nothing was synthesised or deployed. Cure: run npm ${c} in ${e} to diagnose the slow install (network or registry), then retry.`);case"aborted":return d(`Dependency integrity gate: the dependency sync (npm ${c}) in ${e} was cancelled before it completed. Nothing was synthesised or deployed. Cure: re-run the command to restart the sync.`);case"enoent":return d("npm was not found on PATH. Install Node.js 22+ (which bundles npm) on this machine or CI agent, then retry.");case"spawn_error":return d(`Dependency integrity gate: npm ${c} could not be started in ${e} (${h(g.error.message)}). Nothing was synthesised or deployed. Cure: check that the npm binary on PATH exists and is executable, then retry.`)}}o(B,"verifyUncached");export{A as DEPENDENCY_SYNC_TIMEOUT_MS,re as clearDependencyIntegrityCache,se as ensureDependencyIntegrity};
@@ -29,4 +29,11 @@ export interface OrgConfig {
29
29
  * (the primary transport) fails. See `Entitlements.requireTrackedDeploys`.
30
30
  */
31
31
  requireTrackedDeploys?: boolean;
32
+ /**
33
+ * Per-organisation opt-in for the codemod LLM fallback (design
34
+ * 2026-08-10-input-contract-and-surface-governance § 1.4). Absent or false
35
+ * ⇒ fallback disabled: the CLI treats a missing value as opt-out, and the
36
+ * env flag must ALSO be set — enablement is double-off by default.
37
+ */
38
+ allowCodemodLlmFallback?: boolean;
32
39
  }
@@ -57,5 +57,11 @@ export interface DeploymentContext {
57
57
  export interface StepOutput {
58
58
  message: string;
59
59
  status?: "NO_CHANGES" | "SKIPPED";
60
+ /**
61
+ * Pre-masked degradation notes the step succeeded despite (e.g.
62
+ * dependency-gate pin comparisons that were skipped). Callers holding
63
+ * DeployCallbacks re-emit them as typed progress warnings.
64
+ */
65
+ warnings?: string[];
60
66
  details?: Record<string, unknown>;
61
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/deploy-core",
3
- "version": "12.0.0",
3
+ "version": "12.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -96,8 +96,8 @@
96
96
  "@aws-sdk/client-ssm": "^3.1098.0",
97
97
  "@aws-sdk/client-sso-admin": "^3.1098.0",
98
98
  "@aws-sdk/client-sts": "^3.1098.0",
99
- "@fjall/generator": "^12.0.0",
100
- "@fjall/util": "^12.0.0",
99
+ "@fjall/generator": "^12.1.0",
100
+ "@fjall/util": "^12.1.0",
101
101
  "@smithy/node-http-handler": "^4.9.13",
102
102
  "aws-cdk": "^2.1134.0",
103
103
  "tsx": "^4.23.1",