@fjall/deploy-core 21.0.0 → 21.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.
- package/dist/.build-source-hash +15 -14
- package/dist/.minified +1 -1
- package/dist/src/orchestration/application/applicationDeploy.js +1 -1
- package/dist/src/orchestration/application/detectionPipeline.js +1 -1
- package/dist/src/orchestration/application/stalePruneHeal.d.ts +11 -0
- package/dist/src/orchestration/application/stalePruneHeal.js +1 -0
- package/dist/src/orchestration/drift/driftSuspectJournal.js +1 -1
- package/dist/src/orchestration/remediation/forensicsCapture.js +1 -1
- package/dist/src/orchestration/remediation/remediationJournal.js +2 -2
- package/dist/src/services/infrastructure/CfnRegistryService.js +1 -1
- package/dist/src/services/supporting/TemplateHashService.d.ts +15 -0
- package/dist/src/services/supporting/TemplateHashService.js +1 -1
- package/dist/src/types/config/DomainState.js +2 -2
- package/dist/src/types/config/FjallState.js +1 -1
- package/dist/src/types/detection/detection.d.ts +6 -0
- package/package.json +3 -3
package/dist/.build-source-hash
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"package.json",
|
|
10
10
|
"tsconfig.json"
|
|
11
11
|
],
|
|
12
|
-
"hash": "
|
|
12
|
+
"hash": "6b074cf8ff21720ab484a8797152477d50293af193c2ed60801bbd51fcaafd44",
|
|
13
13
|
"files": {
|
|
14
14
|
"src/__test-utils__/awsMockHelpers.ts": "fea371c1b1869678",
|
|
15
15
|
"src/__test-utils__/index.ts": "e03cf35821157dc4",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"src/events/index.ts": "e55c022f0d9013e6",
|
|
55
55
|
"src/index.ts": "2f8c7aa093fea1fc",
|
|
56
56
|
"src/orchestration/activeDeploymentGuard.ts": "e8dde742c00ea728",
|
|
57
|
-
"src/orchestration/application/applicationDeploy.ts": "
|
|
57
|
+
"src/orchestration/application/applicationDeploy.ts": "a45cce4340e63fb9",
|
|
58
58
|
"src/orchestration/application/applicationDeployHelpers.ts": "bca833cc04f0b4c5",
|
|
59
59
|
"src/orchestration/application/applicationDestroy.ts": "38b8ecf69c85d884",
|
|
60
60
|
"src/orchestration/application/approvalGate.ts": "81130a8b58279c45",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"src/orchestration/application/deploySessionPolicy.ts": "6b70e135dd6e71f4",
|
|
70
70
|
"src/orchestration/application/destroyPreFlight.ts": "cc4978dec60b033e",
|
|
71
71
|
"src/orchestration/application/destructionGate.ts": "5fbaa6ef69eb3ae1",
|
|
72
|
-
"src/orchestration/application/detectionPipeline.ts": "
|
|
72
|
+
"src/orchestration/application/detectionPipeline.ts": "9e5acfd35c7f65cb",
|
|
73
73
|
"src/orchestration/application/directDestroy.ts": "4fac968ba765cd76",
|
|
74
74
|
"src/orchestration/application/dockerBuildHelper.ts": "e7a7259e987d93be",
|
|
75
75
|
"src/orchestration/application/ecrCacheRepository.ts": "bf396177c8c66e4a",
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"src/orchestration/application/plan/types.ts": "191929198672081c",
|
|
95
95
|
"src/orchestration/application/schemaGateImage.ts": "0146ae1ce875a9b6",
|
|
96
96
|
"src/orchestration/application/serviceImageTagsRollback.ts": "55e23a493545511c",
|
|
97
|
+
"src/orchestration/application/stalePruneHeal.ts": "2ed2d4c3a14a0710",
|
|
97
98
|
"src/orchestration/application/taskDefinitionRoll.ts": "d9378c509c7ec287",
|
|
98
99
|
"src/orchestration/application/welcomeImageHelper.ts": "1721ea7edc37a96e",
|
|
99
100
|
"src/orchestration/bootstrapTags.ts": "2a7cea931cab808b",
|
|
@@ -128,7 +129,7 @@
|
|
|
128
129
|
"src/orchestration/drift/detectStackDrift.ts": "cc38b12162e08fac",
|
|
129
130
|
"src/orchestration/drift/driftPreFlight.ts": "87c6176636655ad7",
|
|
130
131
|
"src/orchestration/drift/driftProbe.ts": "970ccb185c6efe83",
|
|
131
|
-
"src/orchestration/drift/driftSuspectJournal.ts": "
|
|
132
|
+
"src/orchestration/drift/driftSuspectJournal.ts": "e84523bb5c3dff40",
|
|
132
133
|
"src/orchestration/drift/preFlightGate.ts": "8067a3f474fba0b5",
|
|
133
134
|
"src/orchestration/drift/route53Names.ts": "1ac53cc30ac3260e",
|
|
134
135
|
"src/orchestration/drift/route53RecordPreflight.ts": "f4db16a6c0e69ac9",
|
|
@@ -153,7 +154,7 @@
|
|
|
153
154
|
"src/orchestration/organisationDeploy/trailReconciliation.ts": "7aefb6417c77280b",
|
|
154
155
|
"src/orchestration/probes/deleteBlockProbes.ts": "fa3111386026ca6e",
|
|
155
156
|
"src/orchestration/remediation/driftRepairGate.ts": "988e5ba315d8f94d",
|
|
156
|
-
"src/orchestration/remediation/forensicsCapture.ts": "
|
|
157
|
+
"src/orchestration/remediation/forensicsCapture.ts": "19f3f96ab7900ca7",
|
|
157
158
|
"src/orchestration/remediation/forgetResource.ts": "5509613276ed14d4",
|
|
158
159
|
"src/orchestration/remediation/migrateIdentity.ts": "a2ff062cf3f5c4ee",
|
|
159
160
|
"src/orchestration/remediation/pinIdentity.ts": "4f9f068fbf7832eb",
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
"src/orchestration/remediation/postFailureRepairOffer.ts": "934b3fbd1c4d34d1",
|
|
162
163
|
"src/orchestration/remediation/recreatePreFlight.ts": "654ef18cabe97777",
|
|
163
164
|
"src/orchestration/remediation/recreateResource.ts": "a9d97c47e0af68a7",
|
|
164
|
-
"src/orchestration/remediation/remediationJournal.ts": "
|
|
165
|
+
"src/orchestration/remediation/remediationJournal.ts": "5b97cc4b4220a6de",
|
|
165
166
|
"src/orchestration/remediation/removalUpdate.ts": "5e318c32562e01f2",
|
|
166
167
|
"src/orchestration/remediation/retainFlip.ts": "80e7b47e87251db5",
|
|
167
168
|
"src/orchestration/resolveOperation.ts": "84a9f425939fad63",
|
|
@@ -202,7 +203,7 @@
|
|
|
202
203
|
"src/services/infrastructure/CdkProcessManager.ts": "165c97be53d6de8f",
|
|
203
204
|
"src/services/infrastructure/CdkService.ts": "954ac16a4c646912",
|
|
204
205
|
"src/services/infrastructure/CdkServiceTypes.ts": "f2861947ac9481ab",
|
|
205
|
-
"src/services/infrastructure/CfnRegistryService.ts": "
|
|
206
|
+
"src/services/infrastructure/CfnRegistryService.ts": "ba9c531e6e7212c0",
|
|
206
207
|
"src/services/infrastructure/CloudFormationService.ts": "82ce03614feae2db",
|
|
207
208
|
"src/services/infrastructure/EcrImageInspectorService.ts": "e784440b5680df74",
|
|
208
209
|
"src/services/infrastructure/EcsService.ts": "8a3b6e921bf06c60",
|
|
@@ -221,7 +222,7 @@
|
|
|
221
222
|
"src/services/infrastructure/infraTypecheckPreflight.ts": "09e423a8283e3b8b",
|
|
222
223
|
"src/services/infrastructure/transientNetworkRecovery.ts": "264b9a16fea86869",
|
|
223
224
|
"src/services/supporting/CdkContextBuilder.ts": "ea23e4652bb4f587",
|
|
224
|
-
"src/services/supporting/TemplateHashService.ts": "
|
|
225
|
+
"src/services/supporting/TemplateHashService.ts": "5f24c9f754a35f44",
|
|
225
226
|
"src/services/supporting/helpers.ts": "81c0ba7bfdd99366",
|
|
226
227
|
"src/services/supporting/index.ts": "4bf4e746399d80e3",
|
|
227
228
|
"src/steps/index.ts": "9172adb86bdd1ee5",
|
|
@@ -233,8 +234,8 @@
|
|
|
233
234
|
"src/types/approval.ts": "a9b8c42344246d5c",
|
|
234
235
|
"src/types/callbackKeys.ts": "12daa729d3ee3286",
|
|
235
236
|
"src/types/callbacks.ts": "f529521bf1ca0f4d",
|
|
236
|
-
"src/types/config/DomainState.ts": "
|
|
237
|
-
"src/types/config/FjallState.ts": "
|
|
237
|
+
"src/types/config/DomainState.ts": "2cf01bca4422db83",
|
|
238
|
+
"src/types/config/FjallState.ts": "15383e807ef1bfdd",
|
|
238
239
|
"src/types/config/entitlements.ts": "f69ad28847b67e9a",
|
|
239
240
|
"src/types/config/orgConfig.ts": "4ca02bb3097fa3f2",
|
|
240
241
|
"src/types/constants.ts": "4d77e1ab4925b9a6",
|
|
@@ -250,7 +251,7 @@
|
|
|
250
251
|
"src/types/deployment/parallel.ts": "c06e3ac55fb39eb7",
|
|
251
252
|
"src/types/deploymentEventSchema.ts": "089ffbe9be47297d",
|
|
252
253
|
"src/types/destruction.ts": "0aed5544ebc9cdc0",
|
|
253
|
-
"src/types/detection/detection.ts": "
|
|
254
|
+
"src/types/detection/detection.ts": "3868c5736fdc5b4c",
|
|
254
255
|
"src/types/detection/patternDetection.ts": "6cef991ea486cc94",
|
|
255
256
|
"src/types/detection/patternTypes.ts": "5013f5c0ce1566ac",
|
|
256
257
|
"src/types/drift.ts": "cef9040f5c8f34e2",
|
|
@@ -272,9 +273,9 @@
|
|
|
272
273
|
"src/util/sleepAbortable.ts": "ee2facc7b89ab9f5",
|
|
273
274
|
"src/version.ts": "1811a4475823b27e",
|
|
274
275
|
"../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
|
|
275
|
-
"package.json": "
|
|
276
|
+
"package.json": "f91326ed4eb7af90",
|
|
276
277
|
"tsconfig.json": "1929cf3f99fe9b1e"
|
|
277
278
|
},
|
|
278
|
-
"outputHash": "
|
|
279
|
-
"outputFileCount":
|
|
279
|
+
"outputHash": "1ab9f6ff8c8bcf6df2464cd64ba16005cf478fcf1ef6529b5484d1ac96af2c92",
|
|
280
|
+
"outputFileCount": 515
|
|
280
281
|
}
|
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
257 files minified at 2026-08-28T12:40:34.216Z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var Re=Object.defineProperty;var P=(e,n)=>Re(e,"name",{value:n,configurable:!0});import{join as fe}from"path";import{CloudFormationClient as Ce}from"@aws-sdk/client-cloudformation";import{EC2Client as Ie}from"@aws-sdk/client-ec2";import{success as A,failure as d}from"@fjall/generator";import{logger as F}from"@fjall/util/logger";import{ImageTagSchema as Oe,getErrorMessage as Q,imageTagParameterName as Ae,maskSensitiveOutput as h}from"@fjall/util";import{parseDockerDeclarationsFromManifest as Le}from"@fjall/util/manifest";import{stubCallerIdentity as Be}from"../../types/deployment/index.js";import{getApplicationDeployOrder as Me,getApplicationStackName as I,getApplicationStepName as pe,getApplicationStepId as me,APPLICATION_STACKS as R}from"../../types/operations.js";import{CdkContextBuilder as $e}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as xe,buildCapacityIdentityContext as Fe,bootstrapOrFail as je}from"../contextHelpers.js";import{resolveDeployEnvironment as Ge}from"../deployEnvironment.js";import{buildBootstrapTagsIfResolved as He}from"../bootstrapTags.js";import{hasOrganisationTierAccount as Ue}from"../../aws/organisations/accountGlobals.js";import{runDetectionPipeline as _e}from"./detectionPipeline.js";import{assertCapacityPinsSupportedByAssembly as We,assertEngineCompatibleWithAssembly as Ke}from"./engineCompat.js";import{STEP_IDS as _,STEP_NAMES as qe}from"../../types/stepDefinitions.js";import{StepRegistry as Ve}from"../../steps/stepRegistry.js";import{checkTargetReadiness as ze}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as Je}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as Ye}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as ye,runDockerBuild as Qe}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as Xe,getStackNamesToDeploy as Ze,deployParallelPhase as et,deployStackSequential as tt,runDockerPreCompute as rt,runLambdaVersionPublish as nt,createBuildCallbacks as at,willRunDockerBuild as ot}from"./applicationDeployHelpers.js";import{verifyAssemblyIntegrity as he}from"./assemblyIntegrity.js";import{captureDatabaseConnectionOutputs as it,captureServiceTaskDefinitions as st,reconcileDatabaseEndpointChange as ct}from"./databaseEndpointReconcile.js";import{deployCodeOnly as dt}from"./codeOnlyDeploy.js";import{ensureSchemaGateImage as lt}from"./schemaGateImage.js";import{checkDeployPathStackAvailability as ke}from"../activeDeploymentGuard.js";import{assertLeaseBeforeStack as W}from"./leaseGate.js";import{collectFullDeployArtefacts as ut}from"./artefactCollection.js";import{CfnRegistryService as gt}from"../../services/infrastructure/CfnRegistryService.js";import{ChangeSetProbe as ft}from"../../services/infrastructure/changeSetProbe.js";import{runApprovalGate as pt}from"./approvalGate.js";import{buildDestructionTicket as mt,runDestructionGate as yt}from"./destructionGate.js";import{DESTRUCTION_RESUME_TTL_MS as ht,signApprovalToken as kt}from"./plan/approvalToken.js";import{computeAssemblyDigest as St}from"./plan/assemblyDigest.js";import{createMemoisedPlanLoader as bt}from"./plan/loadDeployPlan.js";import{runDriftPreFlightGate as wt}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as Et}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as Se}from"../remediation/postFailureRepairOffer.js";import{renderPinReportLines as Tt,runPinRemediation as Pt}from"../remediation/pinRemediation.js";import{degradedIdentityPinOutcomes as be,renderIdentityPinReportLines as vt}from"../remediation/migrateIdentity.js";import{runRecreatePreFlight as Nt}from"../remediation/recreatePreFlight.js";import{runRecreateSurgery as Dt}from"../remediation/recreateResource.js";const j="applicationDeploy";function Rt(e){const n=Object.entries(e);if(n.length===0)return;const a={};for(const[t,s]of n){const E=Ae(t);a[E]=s}return a}P(Rt,"buildCdkImageTagParameters");function Ct(e){if(e?.mode!==void 0&&e.mode!=="rollback"){if(e.imageTag)return d(new Error(`--image-tag requires rollback mode (explicit mode '${e.mode}' was supplied alongside an image tag)`));if(e.serviceImageTags)return d(new Error(`serviceImageTags requires rollback mode (explicit mode '${e.mode}' was supplied alongside serviceImageTags)`))}if(e?.mode==="rollback"){if(e.imageTag&&e.serviceImageTags)return d(new Error("rollback mode accepts exactly one of --image-tag or serviceImageTags \u2014 they are mutually exclusive"));if(!e.imageTag&&!e.serviceImageTags)return d(new Error("rollback mode requires --image-tag <tag> or serviceImageTags naming the image(s) to roll to"))}if(e?.serviceImageTags!==void 0){if(e.serviceName)return d(new Error("serviceImageTags already names its services \u2014 --service cannot be combined with it"));const n=Object.entries(e.serviceImageTags);if(n.length===0)return d(new Error("serviceImageTags must name at least one service"));for(const[a,t]of n){const s=Oe.safeParse(t);if(!s.success)return d(new Error(`serviceImageTags.${a} is not a valid image tag: ${s.error.message}`))}}return A(void 0)}P(Ct,"validateRollbackOptions");function we(e,n,a){if(e.onStackTargets===void 0||a.length===0)return;const t=n.awsProvider.getAccountId(),s=n.awsProvider.getRegion();e.onStackTargets(a.map(E=>({stackName:E,...t!==""?{accountId:t}:{},...s!==""?{region:s}:{}})))}P(we,"emitStackTargets");async function wr(e,n,a){const{callbacks:t,options:s}=e,E=Date.now(),G=s?.mode??(s?.imageTag||s?.serviceImageTags?"rollback":s?.deployOnly?"code-only":"full");if(G==="restart"){const r=new Error('mode "restart" is not a deploy \u2014 call the restart() entry point (fjall rollout) instead');return t.onError?.(r),d(r)}const X=Ct(s);if(!X.success){const r=h(X.error.message),o=new Error(r);return t.onError?.(o),d(o)}const K=Ge({orgConfig:e.orgConfig,accountId:n.awsProvider.getAccountId()});if(!K.success){const r=new Error(h(K.error.message));return t.onError?.(r),d(r)}const B=$e.buildDeploymentContext({deployType:"application",target:a.appName,path:a.path,region:n.awsProvider.getRegion(),environment:K.data,callerIdentity:Be(n.awsProvider.getAccountId()),...xe({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc}),...e.managedDomainBindings!==void 0?{managedDomainBindings:e.managedDomainBindings}:{},...e.managedDomainCoverage!==void 0?{managedDomainCoverage:e.managedDomainCoverage}:{},...Fe(e)},{verbose:s?.verbose,infraOnly:s?.infraOnly},e.orgConfig),v=n.frameworkRegistry.resolve({appPath:a.path});let Z;const M=G==="rollback";if(v&&!M){Z=v.builder.plan({appPath:a.path},v.detection);const r=at(t),o=await v.builder.build(a.path,Z,r,{skipBuild:s?.skipBuild,infraOnly:s?.infraOnly,...e.abortSignal!==void 0&&{abortSignal:e.abortSignal}});if(!o.success){const i=new Error(h(o.error.message));return t.onError?.(i),d(i)}}if(G==="code-only"||M){t.onLog?.(M?s?.serviceImageTags?`Rollback mode \u2014 rolling ${Object.keys(s.serviceImageTags).length} service(s) to their release-recorded image tags`:`Rollback mode \u2014 rolling to image tag ${s?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const r=fe(a.path,"cdk.out"),o=Ke({cdkOutPath:r,onWarning:P(f=>t.onLog?.(f,"warn"),"onWarning")});if(!o.success){const f=new Error(h(o.error.message));return t.onError?.(f),d(f)}const i=We({cdkOutPath:r,capacityIdentityWire:B.capacityIdentity,appName:a.appName});if(!i.success){const f=new Error(h(i.error.message));return t.onError?.(f),d(f)}const m=Le(r),l=m.declaresBuild,y=v?.detection.hasDockerfile===!0,g=l||y;F.debug(j,"Deploy-only branch entered",{mode:G,imageTag:s?.imageTag,serviceImageTags:s?.serviceImageTags,appName:a.appName,appPath:a.path,cdkOutPath:r,dockerProviderAvailable:e.dockerProvider!==void 0,builderName:v?.builder.name,hasDockerfileFromManifest:l,hasDockerfileFromDisk:y,hasDockerfile:g,manifestDockerServiceCount:m.ecsServices.length,manifestDockerPaths:m.ecsServices.map(f=>f.docker.path),manifestLambdaDockerEntryCount:m.lambdaEntries.length}),!l&&!y&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");const c=await ke([I(a.appName,R.COMPUTE)],n.cfnService,e.abortSignal);if(!c.success)return t.onError?.(c.error),d(c.error);we(t,n,[I(a.appName,R.COMPUTE)]);let u={},k;if(!M&&e.dockerProvider!==void 0&&g){F.debug(j,"Running Docker build before code-only deploy",{source:l?"manifest":"disk"});const f=await Qe(e,n,a,t);if(!f.success)return d(f.error);u=f.data.contentHashTagsByService,k=f.data}else F.debug(j,"Skipping Docker build",{reason:M?"rollback":e.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});const w=await W(e.assertLeaseForStack,I(a.appName,R.COMPUTE),e.abortSignal);return w!==void 0?(t.onError?.(w),d(w)):dt(e,n,a,u,k)}t.onLog?.("Analysing infrastructure\u2026","info");const H=await _e(a,n,B,t,e.abortSignal);if(!H.success){const r=new Error(h(H.error.message),{cause:H.error});return t.onError?.(r),d(r)}const p=H.data;try{await t.onDetectionComplete?.({...p,builderName:v?.builder.name??"unknown"})}catch(r){const o=new Error(h(Q(r)),{cause:r});return t.onError?.(o),d(o)}const Ee={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:s?.infraOnly??!1,hasDockerfile:p.hasDockerfile,pattern:p.pattern,resources:p.resources,...v&&{builderName:v.builder.name}},ee=Ve.getSteps(Ee),te=ee.findIndex(r=>r.id===_.TARGET_READINESS),re=await Ye(t,{stepId:_.TARGET_READINESS,stepName:qe.TARGET_READINESS,...te>=0&&{stepIndex:te,totalSteps:ee.length}},async()=>{if(s?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const r=n.awsProvider.getAccountId(),o=e.orgConfig?.providerAccounts.find(y=>y.id===r)?.name,i=n.awsProvider.getCredentials(),m=Je(e.orgConfig),l=await ze({cloudFormation:n.awsProvider.getClient(Ce),ec2:new Ie({region:m,...i!==void 0&&{credentials:i}})},{id:r,...o!==void 0&&{name:o}},n.awsProvider.getRegion(),e.orgConfig,e.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(h(l.data.advisory))}:{kind:"error",error:new Error(h(l.error.message),{cause:l.error})}},e.abortSignal);if(!re.success)return re;const N=Me({pattern:p.pattern,resources:p.resources}),L=N.length,ne=p.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(R.COMPUTE)&&s?.infraOnly!==!0;if(!p.hasDifferences&&!s?.force&&!ne){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(s?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const r=p.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(R.COMPUTE);for(let i=0;i<N.length;i++){const m=N[i];r&&m===R.COMPUTE&&(t.onStepStart?.(_.DOCKER_OPERATIONS,ye),t.onStepComplete?.(_.DOCKER_OPERATIONS,ye,"skipped"));const l=me(m,"deploy"),y=pe(m,"deploy");t.onStepStart?.(l,y,i,L),t.onStepComplete?.(l,y,"skipped",i,L)}const o=await n.stackService.resolveWebsiteUrl(a.appName);return A({target:a.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-E})}const O=fe(a.path,"cdk.out"),b=Ze({deployOrder:N,stackChanges:p.stackChanges,appName:a.appName,force:s?.force===!0,computeBuildPossible:ne});we(t,n,[...b]);const q=await ke([...b],n.cfnService,e.abortSignal);if(!q.success)return t.onError?.(q.error),d(q.error);const ae={stacksToDeploy:[...b],imageBuild:ot({stackNamesToDeploy:b,appName:a.appName,hasDockerProvider:e.dockerProvider!==void 0,hasDockerfile:p.hasDockerfile})},oe=St(p.currentHashes,ae),V=bt({cfnService:n.cfnService,cdkOutPath:O,changedStacks:[...b],assemblyDigest:oe,registry:new gt(n.awsProvider),changeSetProbe:new ft(n.awsProvider),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}),z=await wt(n.awsProvider,{stackNames:[...b],cdkOutPath:O,cfnService:n.cfnService,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(z.kind==="blocked"){const r=new Error(z.message);return t.onError?.(r),d(r)}const ie=z.verifiedAbsentByNecessity??[],U=e.approvalGate!==void 0||s?.approvalToken!==void 0&&s.approvalToken!==""||s?.planOnly===!0;if(U){const r=await pt({approvalGate:e.approvalGate,options:s??{},target:a.appName,cdkOutPath:O,detection:{stackChanges:p.stackChanges,currentHashes:p.currentHashes},scope:ae,cfnService:n.cfnService,loadPlan:V,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return r;const o=r.data;if(o.kind==="rejected")return t.onLog?.(`Deploy rejected: ${h(o.reason)}`,"warn"),A({target:a.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-E});if(o.kind==="awaiting"){const i=await V(),m=i.success?mt(i.data,ie):void 0;return A({target:a.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,...m!==void 0?{destructionTicket:m}:{},artefacts:[],durationMs:Date.now()-E})}}const D=await yt({loadPlan:V,options:s??{},target:a.appName,callbacks:t,verifiedAbsentByNecessity:ie,...e.destructionConsentGate!==void 0?{consentGate:e.destructionConsentGate}:{}});if(!D.success)return t.onError?.(D.error),d(D.error);if(D.data.kind==="withheld"){const r=U?kt({assemblyDigest:oe,ttlMs:ht}):void 0;return A({target:a.appName,deploymentType:"application",destructionPending:!0,destructionTicket:D.data.ticket,destructionOutcome:D.data.outcome,destructionHaltReasons:D.data.reasons,...r!==void 0?{destructionResumeToken:r.token,destructionResumeExpiresAt:r.expiresAt}:{},artefacts:[],durationMs:Date.now()-E})}if(b.has(I(a.appName,R.COMPUTE))){const r=await lt({awsProvider:n.awsProvider,cdkOutPath:O,onLog:P((o,i)=>t.onLog?.(o,i),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return t.onError?.(r.error),d(r.error)}if(D.data.kind==="proceed"){const{ticket:r,verbsByPhysicalName:o}=D.data,i=r.resources.filter(l=>o.get(l.physicalName)==="pin");if(i.length>0){const l=i.filter(u=>u.finding==="rename-detected"),y=i.filter(u=>u.finding!=="rename-detected");t.onLog?.(`Pin remediation for ${a.appName}: ${i.length} consented pin(s) \u2014 deploy stopped for config write-back; nothing was applied to AWS`,"warn");let g;if(l.length>0){if(e.identityPinRunner!==void 0)try{g=await e.identityPinRunner(l,e.abortSignal)}catch(u){g={outcomes:be(l,"unknown",`identity-pin runner failed: ${h(Q(u))} \u2014 fix the cause, then run 'fjall migrate identity'`),verified:[],clean:!1}}else g={outcomes:be(l,"unknown","this surface cannot run the identity-pin migration \u2014 run 'fjall migrate identity' to pin and validate this slot"),verified:[],clean:!1};for(const u of vt(g))t.onLog?.(u,"warn")}let c;if(y.length>0){c=await Pt(n.awsProvider,{targets:y.map(u=>({stack:u.stack,logicalId:u.logicalId,resourceType:u.resourceType,physicalName:u.physicalName,offendingProperties:u.offendingProperties})),...e.infrastructureSource!==void 0?{infrastructureContent:e.infrastructureSource.content,infrastructureFilePath:e.infrastructureSource.filePath}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});for(const u of Tt(c))t.onLog?.(u,"warn")}return i.length<r.resources.length&&t.onLog?.("Remaining consented change(s) were not applied \u2014 the config write-back changes the plan, so they re-consent on the next deploy","warn"),A({target:a.appName,deploymentType:"application",pinPending:!0,...c!==void 0?{pinReport:c}:{},...g!==void 0?{identityPinReport:g}:{},pinDeferredConsents:r.resources.length-i.length,destructionTicket:r,artefacts:[],durationMs:Date.now()-E})}const m=r.resources.filter(l=>l.finding==="pinned-name-replacement"&&o.get(l.physicalName)==="recreate");if(m.length>0){const l=new Map;for(const c of m){const u=l.get(c.stack)??[];u.push(c),l.set(c.stack,u)}const y=await Nt(n.awsProvider,{stacks:[...l].map(([c,u])=>({stackName:c,targets:u.map(k=>({logicalId:k.logicalId,resourceType:k.resourceType,physicalId:k.physicalName}))})),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!y.success){const c=new Error(h(y.error.message),{cause:y.error});return t.onError?.(c),d(c)}const g=[];for(const[c,u]of l){const k=await W(e.assertLeaseForStack,c,e.abortSignal,"destroy");if(k!==void 0)return g.length>0&&t.onLog?.(`Surgery already completed on ${g.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`,"warn"),t.onError?.(k),d(k);const w=u.map(S=>S.physicalName);t.onLog?.(`Recreate remediation for ${a.appName}: orchestrating the pinned-name replacement of ${w.join(", ")} on ${c}`,"warn");const f=await Dt(n.awsProvider,{stackName:c,targets:u.map(S=>({logicalId:S.logicalId,resourceType:S.resourceType,physicalId:S.physicalName})),appName:a.appName,consentedPhysicalNames:w,doneJournalPolicy:"archive-and-restart",plannedReplacementLogicalIds:u.map(S=>S.logicalId),onProgress:P(S=>t.onLog?.(S,"info"),"onProgress"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!f.success){const S=g.length>0?` Surgery already completed on ${g.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`:"",T=new Error(h(`${f.error.message}${S}`));return t.onError?.(T),d(T)}g.push(`${c} (${w.join(", ")})`)}}}const Te=e.orgConfig!==void 0&&!Ue(e.orgConfig.providerAccounts),Pe=He({account:e.orgConfig?.providerAccounts.find(r=>r.id===n.awsProvider.getAccountId()),estate:Te?"solo":"org"}),se=await je(n,B,t,Pe);if(!se.success)return se;const ce=I(a.appName,R.DATABASE),J=b.has(ce),ve=J?await it(n.cfnService,a.appName,e.abortSignal):void 0,Ne=J?await st(n.ecsResolver,n.ecsService,a.appName,e.abortSignal):void 0,$={},x=new Map;let de,le={};const C=P(async()=>{!J||!x.has(ce)||await ct({appName:a.appName,connectionOutputsBefore:ve,serviceTaskDefinitionsBefore:Ne,services:{cfnService:n.cfnService,ecsService:n.ecsService,ecsResolver:n.ecsResolver},callbacks:t,...e.confirmDatabaseEndpointRestart!==void 0?{confirmRestart:e.confirmDatabaseEndpointRestart}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}})},"reconcileIfDatabaseReplaced");for(let r=0;r<N.length;r++){const o=N[r],i=I(a.appName,o),m=me(o,"deploy"),l=pe(o,"deploy");if(!b.has(i)){t.onStepStart?.(m,l,r,L),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(m,l,"skipped",r,L);continue}const y=await W(e.assertLeaseForStack,i,e.abortSignal);if(y!==void 0)return await C(),t.onError?.(y),d(y);const g=Xe(N,r,a.appName,b);if(g.length>=2){const T=await he({hashService:n.hashService,cdkOutPath:O,stackNames:g.map(Y=>I(a.appName,Y)),detectionHashes:p.currentHashes,approvalGated:U});if(!T.success)return await C(),t.onError?.(T.error),d(T.error);const ge=await et(g,a,n,B,t,r,L,p,$,x);if(!ge.success)return await C(),d(await Se(n.awsProvider,{stackNames:g.map(Y=>I(a.appName,Y)),appName:a.appName,originalError:ge.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));r+=g.length-1;continue}const c=await rt(o,e,n,a,t,p.hasDockerfile);if(c!==null&&!c.success)return await C(),d(c.error);c!==null&&c.success&&(de=c.data);const u=c!==null&&c.success?c.data.contentHashTagsByService:{},k=Rt(u);if(c!==null){const T=await W(e.assertLeaseForStack,i,e.abortSignal);if(T!==void 0)return await C(),t.onError?.(T),d(T)}const w=await he({hashService:n.hashService,cdkOutPath:O,stackNames:[i],detectionHashes:p.currentHashes,approvalGated:U});if(!w.success)return await C(),t.onError?.(w.error),d(w.error);const f=await tt(o,n,B,t,r,L,$,k!==void 0||e.abortSignal!==void 0?{...k!==void 0?{parameters:k}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}:void 0);if(!f.success)return await C(),d(await Se(n.awsProvider,{stackNames:[i],appName:a.appName,originalError:f.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));o===R.COMPUTE&&(le=await nt(a,n,t,u,e.abortSignal));const S=p.currentHashes.get(i);S&&x.set(i,S)}if(await C(),x.size>0){const r=await n.hashService.updateStateAfterDeploy(a.path,x);if(!r.success){const o=h(r.error.message);F.debug(j,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const ue=await n.stackService.resolveWebsiteUrl(a.appName);ue&&($.websiteUrl=ue);const De=await ut(n,a.appName,de,t,O,le,e.abortSignal);return await Et(n.awsProvider,{stackNames:[...b],appName:a.appName,onLog:P((r,o)=>t.onLog?.(r,o),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}).catch(r=>{F.debug(j,"Forget completion sweep failed",{error:h(Q(r))})}),A({target:a.appName,deploymentType:"application",outputs:Object.keys($).length>0?$:void 0,artefacts:De,durationMs:Date.now()-E})}P(wr,"deployApplication");export{wr as deployApplication,we as emitStackTargets};
|
|
1
|
+
var Ce=Object.defineProperty;var P=(e,n)=>Ce(e,"name",{value:n,configurable:!0});import{join as fe}from"path";import{CloudFormationClient as Ie}from"@aws-sdk/client-cloudformation";import{EC2Client as Oe}from"@aws-sdk/client-ec2";import{success as A,failure as l}from"@fjall/generator";import{logger as F}from"@fjall/util/logger";import{ImageTagSchema as Ae,getErrorMessage as Q,imageTagParameterName as Le,maskSensitiveOutput as h}from"@fjall/util";import{parseDockerDeclarationsFromManifest as Be}from"@fjall/util/manifest";import{stubCallerIdentity as Me}from"../../types/deployment/index.js";import{getApplicationDeployOrder as $e,getApplicationStackName as I,getApplicationStepName as pe,getApplicationStepId as me,APPLICATION_STACKS as R}from"../../types/operations.js";import{CdkContextBuilder as xe}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as Fe,buildCapacityIdentityContext as je,bootstrapOrFail as Ge}from"../contextHelpers.js";import{resolveDeployEnvironment as He}from"../deployEnvironment.js";import{buildBootstrapTagsIfResolved as Ue}from"../bootstrapTags.js";import{hasOrganisationTierAccount as _e}from"../../aws/organisations/accountGlobals.js";import{runDetectionPipeline as We}from"./detectionPipeline.js";import{persistStaleStatePrune as ye}from"./stalePruneHeal.js";import{assertCapacityPinsSupportedByAssembly as Ke,assertEngineCompatibleWithAssembly as qe}from"./engineCompat.js";import{STEP_IDS as _,STEP_NAMES as Ve}from"../../types/stepDefinitions.js";import{StepRegistry as ze}from"../../steps/stepRegistry.js";import{checkTargetReadiness as Je}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as Ye}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as Qe}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as he,runDockerBuild as Xe}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as Ze,getStackNamesToDeploy as et,deployParallelPhase as tt,deployStackSequential as rt,runDockerPreCompute as nt,runLambdaVersionPublish as at,createBuildCallbacks as ot,willRunDockerBuild as it}from"./applicationDeployHelpers.js";import{verifyAssemblyIntegrity as ke}from"./assemblyIntegrity.js";import{captureDatabaseConnectionOutputs as st,captureServiceTaskDefinitions as ct,reconcileDatabaseEndpointChange as lt}from"./databaseEndpointReconcile.js";import{deployCodeOnly as dt}from"./codeOnlyDeploy.js";import{ensureSchemaGateImage as ut}from"./schemaGateImage.js";import{checkDeployPathStackAvailability as Se}from"../activeDeploymentGuard.js";import{assertLeaseBeforeStack as W}from"./leaseGate.js";import{collectFullDeployArtefacts as gt}from"./artefactCollection.js";import{CfnRegistryService as ft}from"../../services/infrastructure/CfnRegistryService.js";import{ChangeSetProbe as pt}from"../../services/infrastructure/changeSetProbe.js";import{runApprovalGate as mt}from"./approvalGate.js";import{buildDestructionTicket as yt,runDestructionGate as ht}from"./destructionGate.js";import{DESTRUCTION_RESUME_TTL_MS as kt,signApprovalToken as St}from"./plan/approvalToken.js";import{computeAssemblyDigest as bt}from"./plan/assemblyDigest.js";import{createMemoisedPlanLoader as wt}from"./plan/loadDeployPlan.js";import{runDriftPreFlightGate as Et}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as Tt}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as be}from"../remediation/postFailureRepairOffer.js";import{renderPinReportLines as Pt,runPinRemediation as vt}from"../remediation/pinRemediation.js";import{degradedIdentityPinOutcomes as we,renderIdentityPinReportLines as Nt}from"../remediation/migrateIdentity.js";import{runRecreatePreFlight as Dt}from"../remediation/recreatePreFlight.js";import{runRecreateSurgery as Rt}from"../remediation/recreateResource.js";const j="applicationDeploy";function Ct(e){const n=Object.entries(e);if(n.length===0)return;const a={};for(const[t,i]of n){const E=Le(t);a[E]=i}return a}P(Ct,"buildCdkImageTagParameters");function It(e){if(e?.mode!==void 0&&e.mode!=="rollback"){if(e.imageTag)return l(new Error(`--image-tag requires rollback mode (explicit mode '${e.mode}' was supplied alongside an image tag)`));if(e.serviceImageTags)return l(new Error(`serviceImageTags requires rollback mode (explicit mode '${e.mode}' was supplied alongside serviceImageTags)`))}if(e?.mode==="rollback"){if(e.imageTag&&e.serviceImageTags)return l(new Error("rollback mode accepts exactly one of --image-tag or serviceImageTags \u2014 they are mutually exclusive"));if(!e.imageTag&&!e.serviceImageTags)return l(new Error("rollback mode requires --image-tag <tag> or serviceImageTags naming the image(s) to roll to"))}if(e?.serviceImageTags!==void 0){if(e.serviceName)return l(new Error("serviceImageTags already names its services \u2014 --service cannot be combined with it"));const n=Object.entries(e.serviceImageTags);if(n.length===0)return l(new Error("serviceImageTags must name at least one service"));for(const[a,t]of n){const i=Ae.safeParse(t);if(!i.success)return l(new Error(`serviceImageTags.${a} is not a valid image tag: ${i.error.message}`))}}return A(void 0)}P(It,"validateRollbackOptions");function Ee(e,n,a){if(e.onStackTargets===void 0||a.length===0)return;const t=n.awsProvider.getAccountId(),i=n.awsProvider.getRegion();e.onStackTargets(a.map(E=>({stackName:E,...t!==""?{accountId:t}:{},...i!==""?{region:i}:{}})))}P(Ee,"emitStackTargets");async function Tr(e,n,a){const{callbacks:t,options:i}=e,E=Date.now(),G=i?.mode??(i?.imageTag||i?.serviceImageTags?"rollback":i?.deployOnly?"code-only":"full");if(G==="restart"){const r=new Error('mode "restart" is not a deploy \u2014 call the restart() entry point (fjall rollout) instead');return t.onError?.(r),l(r)}const X=It(i);if(!X.success){const r=h(X.error.message),o=new Error(r);return t.onError?.(o),l(o)}const K=He({orgConfig:e.orgConfig,accountId:n.awsProvider.getAccountId()});if(!K.success){const r=new Error(h(K.error.message));return t.onError?.(r),l(r)}const B=xe.buildDeploymentContext({deployType:"application",target:a.appName,path:a.path,region:n.awsProvider.getRegion(),environment:K.data,callerIdentity:Me(n.awsProvider.getAccountId()),...Fe({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc}),...e.managedDomainBindings!==void 0?{managedDomainBindings:e.managedDomainBindings}:{},...e.managedDomainCoverage!==void 0?{managedDomainCoverage:e.managedDomainCoverage}:{},...je(e)},{verbose:i?.verbose,infraOnly:i?.infraOnly},e.orgConfig),v=n.frameworkRegistry.resolve({appPath:a.path});let Z;const M=G==="rollback";if(v&&!M){Z=v.builder.plan({appPath:a.path},v.detection);const r=ot(t),o=await v.builder.build(a.path,Z,r,{skipBuild:i?.skipBuild,infraOnly:i?.infraOnly,...e.abortSignal!==void 0&&{abortSignal:e.abortSignal}});if(!o.success){const s=new Error(h(o.error.message));return t.onError?.(s),l(s)}}if(G==="code-only"||M){t.onLog?.(M?i?.serviceImageTags?`Rollback mode \u2014 rolling ${Object.keys(i.serviceImageTags).length} service(s) to their release-recorded image tags`:`Rollback mode \u2014 rolling to image tag ${i?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const r=fe(a.path,"cdk.out"),o=qe({cdkOutPath:r,onWarning:P(p=>t.onLog?.(p,"warn"),"onWarning")});if(!o.success){const p=new Error(h(o.error.message));return t.onError?.(p),l(p)}const s=Ke({cdkOutPath:r,capacityIdentityWire:B.capacityIdentity,appName:a.appName});if(!s.success){const p=new Error(h(s.error.message));return t.onError?.(p),l(p)}const m=Be(r),d=m.declaresBuild,y=v?.detection.hasDockerfile===!0,f=d||y;F.debug(j,"Deploy-only branch entered",{mode:G,imageTag:i?.imageTag,serviceImageTags:i?.serviceImageTags,appName:a.appName,appPath:a.path,cdkOutPath:r,dockerProviderAvailable:e.dockerProvider!==void 0,builderName:v?.builder.name,hasDockerfileFromManifest:d,hasDockerfileFromDisk:y,hasDockerfile:f,manifestDockerServiceCount:m.ecsServices.length,manifestDockerPaths:m.ecsServices.map(p=>p.docker.path),manifestLambdaDockerEntryCount:m.lambdaEntries.length}),!d&&!y&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");const c=await Se([I(a.appName,R.COMPUTE)],n.cfnService,e.abortSignal);if(!c.success)return t.onError?.(c.error),l(c.error);Ee(t,n,[I(a.appName,R.COMPUTE)]);let u={},k;if(!M&&e.dockerProvider!==void 0&&f){F.debug(j,"Running Docker build before code-only deploy",{source:d?"manifest":"disk"});const p=await Xe(e,n,a,t);if(!p.success)return l(p.error);u=p.data.contentHashTagsByService,k=p.data}else F.debug(j,"Skipping Docker build",{reason:M?"rollback":e.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});const w=await W(e.assertLeaseForStack,I(a.appName,R.COMPUTE),e.abortSignal);return w!==void 0?(t.onError?.(w),l(w)):dt(e,n,a,u,k)}t.onLog?.("Analysing infrastructure\u2026","info");const H=await We(a,n,B,t,e.abortSignal);if(!H.success){const r=new Error(h(H.error.message),{cause:H.error});return t.onError?.(r),l(r)}const g=H.data;try{await t.onDetectionComplete?.({...g,builderName:v?.builder.name??"unknown"})}catch(r){const o=new Error(h(Q(r)),{cause:r});return t.onError?.(o),l(o)}const Te={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:i?.infraOnly??!1,hasDockerfile:g.hasDockerfile,pattern:g.pattern,resources:g.resources,...v&&{builderName:v.builder.name}},ee=ze.getSteps(Te),te=ee.findIndex(r=>r.id===_.TARGET_READINESS),re=await Qe(t,{stepId:_.TARGET_READINESS,stepName:Ve.TARGET_READINESS,...te>=0&&{stepIndex:te,totalSteps:ee.length}},async()=>{if(i?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const r=n.awsProvider.getAccountId(),o=e.orgConfig?.providerAccounts.find(y=>y.id===r)?.name,s=n.awsProvider.getCredentials(),m=Ye(e.orgConfig),d=await Je({cloudFormation:n.awsProvider.getClient(Ie),ec2:new Oe({region:m,...s!==void 0&&{credentials:s}})},{id:r,...o!==void 0&&{name:o}},n.awsProvider.getRegion(),e.orgConfig,e.abortSignal);return d.success?d.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(h(d.data.advisory))}:{kind:"error",error:new Error(h(d.error.message),{cause:d.error})}},e.abortSignal);if(!re.success)return re;const N=$e({pattern:g.pattern,resources:g.resources}),L=N.length,ne=g.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(R.COMPUTE)&&i?.infraOnly!==!0;if(!g.hasDifferences&&!i?.force&&!ne){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(i?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const r=g.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(R.COMPUTE);for(let s=0;s<N.length;s++){const m=N[s];r&&m===R.COMPUTE&&(t.onStepStart?.(_.DOCKER_OPERATIONS,he),t.onStepComplete?.(_.DOCKER_OPERATIONS,he,"skipped"));const d=me(m,"deploy"),y=pe(m,"deploy");t.onStepStart?.(d,y,s,L),t.onStepComplete?.(d,y,"skipped",s,L)}i?.planOnly!==!0&&await ye(n,a.path,g.staleStateEntries,t);const o=await n.stackService.resolveWebsiteUrl(a.appName);return A({target:a.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-E})}const O=fe(a.path,"cdk.out"),b=et({deployOrder:N,stackChanges:g.stackChanges,appName:a.appName,force:i?.force===!0,computeBuildPossible:ne});Ee(t,n,[...b]);const q=await Se([...b],n.cfnService,e.abortSignal);if(!q.success)return t.onError?.(q.error),l(q.error);const ae={stacksToDeploy:[...b],imageBuild:it({stackNamesToDeploy:b,appName:a.appName,hasDockerProvider:e.dockerProvider!==void 0,hasDockerfile:g.hasDockerfile})},oe=bt(g.currentHashes,ae),V=wt({cfnService:n.cfnService,cdkOutPath:O,changedStacks:[...b],assemblyDigest:oe,registry:new ft(n.awsProvider),changeSetProbe:new pt(n.awsProvider),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}),z=await Et(n.awsProvider,{stackNames:[...b],cdkOutPath:O,cfnService:n.cfnService,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(z.kind==="blocked"){const r=new Error(z.message);return t.onError?.(r),l(r)}const ie=z.verifiedAbsentByNecessity??[],U=e.approvalGate!==void 0||i?.approvalToken!==void 0&&i.approvalToken!==""||i?.planOnly===!0;if(U){const r=await mt({approvalGate:e.approvalGate,options:i??{},target:a.appName,cdkOutPath:O,detection:{stackChanges:g.stackChanges,currentHashes:g.currentHashes},scope:ae,cfnService:n.cfnService,loadPlan:V,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return r;const o=r.data;if(o.kind==="rejected")return t.onLog?.(`Deploy rejected: ${h(o.reason)}`,"warn"),A({target:a.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-E});if(o.kind==="awaiting"){const s=await V(),m=s.success?yt(s.data,ie):void 0;return A({target:a.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,...m!==void 0?{destructionTicket:m}:{},artefacts:[],durationMs:Date.now()-E})}}const D=await ht({loadPlan:V,options:i??{},target:a.appName,callbacks:t,verifiedAbsentByNecessity:ie,...e.destructionConsentGate!==void 0?{consentGate:e.destructionConsentGate}:{}});if(!D.success)return t.onError?.(D.error),l(D.error);if(D.data.kind==="withheld"){const r=U?St({assemblyDigest:oe,ttlMs:kt}):void 0;return A({target:a.appName,deploymentType:"application",destructionPending:!0,destructionTicket:D.data.ticket,destructionOutcome:D.data.outcome,destructionHaltReasons:D.data.reasons,...r!==void 0?{destructionResumeToken:r.token,destructionResumeExpiresAt:r.expiresAt}:{},artefacts:[],durationMs:Date.now()-E})}if(b.has(I(a.appName,R.COMPUTE))){const r=await ut({awsProvider:n.awsProvider,cdkOutPath:O,onLog:P((o,s)=>t.onLog?.(o,s),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return t.onError?.(r.error),l(r.error)}if(D.data.kind==="proceed"){const{ticket:r,verbsByPhysicalName:o}=D.data,s=r.resources.filter(d=>o.get(d.physicalName)==="pin");if(s.length>0){const d=s.filter(u=>u.finding==="rename-detected"),y=s.filter(u=>u.finding!=="rename-detected");t.onLog?.(`Pin remediation for ${a.appName}: ${s.length} consented pin(s) \u2014 deploy stopped for config write-back; nothing was applied to AWS`,"warn");let f;if(d.length>0){if(e.identityPinRunner!==void 0)try{f=await e.identityPinRunner(d,e.abortSignal)}catch(u){f={outcomes:we(d,"unknown",`identity-pin runner failed: ${h(Q(u))} \u2014 fix the cause, then run 'fjall migrate identity'`),verified:[],clean:!1}}else f={outcomes:we(d,"unknown","this surface cannot run the identity-pin migration \u2014 run 'fjall migrate identity' to pin and validate this slot"),verified:[],clean:!1};for(const u of Nt(f))t.onLog?.(u,"warn")}let c;if(y.length>0){c=await vt(n.awsProvider,{targets:y.map(u=>({stack:u.stack,logicalId:u.logicalId,resourceType:u.resourceType,physicalName:u.physicalName,offendingProperties:u.offendingProperties})),...e.infrastructureSource!==void 0?{infrastructureContent:e.infrastructureSource.content,infrastructureFilePath:e.infrastructureSource.filePath}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});for(const u of Pt(c))t.onLog?.(u,"warn")}return s.length<r.resources.length&&t.onLog?.("Remaining consented change(s) were not applied \u2014 the config write-back changes the plan, so they re-consent on the next deploy","warn"),A({target:a.appName,deploymentType:"application",pinPending:!0,...c!==void 0?{pinReport:c}:{},...f!==void 0?{identityPinReport:f}:{},pinDeferredConsents:r.resources.length-s.length,destructionTicket:r,artefacts:[],durationMs:Date.now()-E})}const m=r.resources.filter(d=>d.finding==="pinned-name-replacement"&&o.get(d.physicalName)==="recreate");if(m.length>0){const d=new Map;for(const c of m){const u=d.get(c.stack)??[];u.push(c),d.set(c.stack,u)}const y=await Dt(n.awsProvider,{stacks:[...d].map(([c,u])=>({stackName:c,targets:u.map(k=>({logicalId:k.logicalId,resourceType:k.resourceType,physicalId:k.physicalName}))})),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!y.success){const c=new Error(h(y.error.message),{cause:y.error});return t.onError?.(c),l(c)}const f=[];for(const[c,u]of d){const k=await W(e.assertLeaseForStack,c,e.abortSignal,"destroy");if(k!==void 0)return f.length>0&&t.onLog?.(`Surgery already completed on ${f.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`,"warn"),t.onError?.(k),l(k);const w=u.map(S=>S.physicalName);t.onLog?.(`Recreate remediation for ${a.appName}: orchestrating the pinned-name replacement of ${w.join(", ")} on ${c}`,"warn");const p=await Rt(n.awsProvider,{stackName:c,targets:u.map(S=>({logicalId:S.logicalId,resourceType:S.resourceType,physicalId:S.physicalName})),appName:a.appName,consentedPhysicalNames:w,doneJournalPolicy:"archive-and-restart",plannedReplacementLogicalIds:u.map(S=>S.logicalId),onProgress:P(S=>t.onLog?.(S,"info"),"onProgress"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!p.success){const S=f.length>0?` Surgery already completed on ${f.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`:"",T=new Error(h(`${p.error.message}${S}`));return t.onError?.(T),l(T)}f.push(`${c} (${w.join(", ")})`)}}}const Pe=e.orgConfig!==void 0&&!_e(e.orgConfig.providerAccounts),ve=Ue({account:e.orgConfig?.providerAccounts.find(r=>r.id===n.awsProvider.getAccountId()),estate:Pe?"solo":"org"}),se=await Ge(n,B,t,ve);if(!se.success)return se;const ce=I(a.appName,R.DATABASE),J=b.has(ce),Ne=J?await st(n.cfnService,a.appName,e.abortSignal):void 0,De=J?await ct(n.ecsResolver,n.ecsService,a.appName,e.abortSignal):void 0,$={},x=new Map;let le,de={};const C=P(async()=>{!J||!x.has(ce)||await lt({appName:a.appName,connectionOutputsBefore:Ne,serviceTaskDefinitionsBefore:De,services:{cfnService:n.cfnService,ecsService:n.ecsService,ecsResolver:n.ecsResolver},callbacks:t,...e.confirmDatabaseEndpointRestart!==void 0?{confirmRestart:e.confirmDatabaseEndpointRestart}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}})},"reconcileIfDatabaseReplaced");for(let r=0;r<N.length;r++){const o=N[r],s=I(a.appName,o),m=me(o,"deploy"),d=pe(o,"deploy");if(!b.has(s)){t.onStepStart?.(m,d,r,L),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(m,d,"skipped",r,L);continue}const y=await W(e.assertLeaseForStack,s,e.abortSignal);if(y!==void 0)return await C(),t.onError?.(y),l(y);const f=Ze(N,r,a.appName,b);if(f.length>=2){const T=await ke({hashService:n.hashService,cdkOutPath:O,stackNames:f.map(Y=>I(a.appName,Y)),detectionHashes:g.currentHashes,approvalGated:U});if(!T.success)return await C(),t.onError?.(T.error),l(T.error);const ge=await tt(f,a,n,B,t,r,L,g,$,x);if(!ge.success)return await C(),l(await be(n.awsProvider,{stackNames:f.map(Y=>I(a.appName,Y)),appName:a.appName,originalError:ge.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));r+=f.length-1;continue}const c=await nt(o,e,n,a,t,g.hasDockerfile);if(c!==null&&!c.success)return await C(),l(c.error);c!==null&&c.success&&(le=c.data);const u=c!==null&&c.success?c.data.contentHashTagsByService:{},k=Ct(u);if(c!==null){const T=await W(e.assertLeaseForStack,s,e.abortSignal);if(T!==void 0)return await C(),t.onError?.(T),l(T)}const w=await ke({hashService:n.hashService,cdkOutPath:O,stackNames:[s],detectionHashes:g.currentHashes,approvalGated:U});if(!w.success)return await C(),t.onError?.(w.error),l(w.error);const p=await rt(o,n,B,t,r,L,$,k!==void 0||e.abortSignal!==void 0?{...k!==void 0?{parameters:k}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}:void 0);if(!p.success)return await C(),l(await be(n.awsProvider,{stackNames:[s],appName:a.appName,originalError:p.error,cfnService:n.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));o===R.COMPUTE&&(de=await at(a,n,t,u,e.abortSignal));const S=g.currentHashes.get(s);S&&x.set(s,S)}if(await C(),x.size>0){const r=await n.hashService.updateStateAfterDeploy(a.path,x);if(!r.success){const o=h(r.error.message);F.debug(j,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}await ye(n,a.path,g.staleStateEntries,t);const ue=await n.stackService.resolveWebsiteUrl(a.appName);ue&&($.websiteUrl=ue);const Re=await gt(n,a.appName,le,t,O,de,e.abortSignal);return await Tt(n.awsProvider,{stackNames:[...b],appName:a.appName,onLog:P((r,o)=>t.onLog?.(r,o),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}).catch(r=>{F.debug(j,"Forget completion sweep failed",{error:h(Q(r))})}),A({target:a.appName,deploymentType:"application",outputs:Object.keys($).length>0?$:void 0,artefacts:Re,durationMs:Date.now()-E})}P(Tr,"deployApplication");export{Tr as deployApplication,Ee as emitStackTargets};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var I=Object.defineProperty;var C=(a,r)=>I(a,"name",{value:r,configurable:!0});import{join as q}from"path";import{success as B,failure as o}from"@fjall/generator";import{logger as A}from"@fjall/util/logger";import{maskSensitiveOutput as n}from"@fjall/util";import{deriveResourcesFromManifestStacks as _}from"../../types/detection/patternDetection.js";import{emitProgress as N,PROGRESS_MESSAGES as v}from"../../services/supporting/helpers.js";import{parseRequiredSecretsFromManifest as j,parseImportedSecretNamesFromManifest as G}from"../manifestSecretParser.js";import{resolveSecretArns as K}from"../secretArnResolver.js";import{emitStepWarnings as $}from"../contextHelpers.js";import{isAborted as O}from"../../aws/organisations/types.js";import{parseDockerDeclarationsFromManifest as x}from"@fjall/util/manifest";import{assertCapacityPinsSupportedByAssembly as z,assertEngineCompatibleWithAssembly as J}from"./engineCompat.js";async function ae(a,r,d,e,m){e.onDetectionPhaseChange?.("detect","started");const w=r.frameworkRegistry.resolve({appPath:a.path}),P=w?.detection.pattern??null,F=w?.detection.hasDatabase??!1;e.onLog?.(`Pattern detected: ${P??"standard"}`,"info"),e.onDetectionPhaseChange?.("detect","completed");const i=q(a.path,"cdk.out");e.onDetectionPhaseChange?.("synth","started"),N(e,v.SYNTH);const p=await r.cdkService.runCdkSynth(d,t=>e.onCdkOutput?.(t,"synth"));if(!p.success)return o(new Error(n(`CDK synthesis failed: ${p.error}`)));if($(e,p.data),e.onDetectionPhaseChange?.("synth","completed"),d.resolvedSecretArns===void 0){const t=G(i);if(t.length>0){e.onLog?.(`Resolving ${t.length} imported secret ARN(s)\u2026`,"info");const s=await K(r.awsProvider,t);if(!s.success)return o(new Error(n(s.error.message)));d.resolvedSecretArns=JSON.stringify(s.data);const u=await r.cdkService.runCdkSynth(d,W=>e.onCdkOutput?.(W,"synth"));if(!u.success)return o(new Error(n(`CDK synthesis failed: ${u.error}`)));$(e,u.data)}}const D=J({cdkOutPath:i,onWarning:C(t=>e.onLog?.(t,"warn"),"onWarning")});if(!D.success)return o(new Error(n(D.error.message)));const E=z({cdkOutPath:i,capacityIdentityWire:d.capacityIdentity,appName:a.appName});if(!E.success)return o(new Error(n(E.error.message)));const g=j(i);g.length>0&&e.onLog?.(`Found ${g.length} required secret path(s) in manifest`,"info"),e.onDetectionPhaseChange?.("hash","started"),N(e,v.HASH);const S=await r.hashService.getTemplateHashes(i);if(!S.success)return o(new Error(n(`Template hash computation failed: ${S.error.message}`)));const f=S.data,y=await r.hashService.compareWithState(f,a.path);if(!y.success)return o(new Error(n(`Hash comparison failed: ${y.error.message}`)));const R=y.data;e.onDetectionPhaseChange?.("hash","completed");const c=new Map(R.stackChanges);for(const[t,s]of R.stackChanges){if(O(m))break;s||await r.cfnService.stackExists(t,void 0,m)||(A.debug("detectionPipeline","Stale hash detected \u2014 stack missing in CFN",{stackName:t}),c.set(t,!0))}const H=Array.from(c.keys()).filter(t=>!f.has(t)),h=[];for(const t of H){if(O(m))break;const s=await r.cfnService.getStackStatus(t,void 0,m);s.success&&s.data?.status==="DOES_NOT_EXIST"?(c.delete(t),h.push(t)):s.success||A.debug("detectionPipeline","Presence probe failed for state-only stack \u2014 keeping its entry",{stackName:t,error:n(s.error.message)})}h.length>0&&e.onLog?.(`Stale state entries detected for removed stacks no longer in CloudFormation: ${h.join(", ")} \u2014 pruned on the next deploy`,"info");const L=Array.from(f.keys()),k=_(L),M=k.hasCompute&&x(i).declaresBuild,l=Array.from(c.values()).filter(Boolean).length,T=l>0;return e.onLog?.(`Detection complete: ${l} changed, ${c.size-l} unchanged`,"info"),B({pattern:P,hasDatabase:F,hasDifferences:T,stackChanges:c,currentHashes:f,resources:k,hasDockerfile:M,requiredSecrets:g,staleStateEntries:h})}C(ae,"runDetectionPipeline");export{ae as runDetectionPipeline};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DeployCallbacks } from "../../types/callbacks.js";
|
|
2
|
+
import type { DeployServices } from "../serviceFactory.js";
|
|
3
|
+
/**
|
|
4
|
+
* Persist the phantom-state heal that detection may only compute in-memory:
|
|
5
|
+
* detection is read-only (plan/apply split), so the state-file prune for
|
|
6
|
+
* removed stacks verified absent from CloudFormation lands here, and callers
|
|
7
|
+
* invoke it only from apply-authorised paths (never plan-only or rejected).
|
|
8
|
+
* A no-op prune is never reported as a heal, and a failed write degrades
|
|
9
|
+
* visibly — the entries simply re-detect as stale on the next plan.
|
|
10
|
+
*/
|
|
11
|
+
export declare function persistStaleStatePrune(services: Pick<DeployServices, "hashService">, appPath: string, staleStateEntries: readonly string[], callbacks: DeployCallbacks): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var s=Object.defineProperty;var i=(n,o)=>s(n,"name",{value:o,configurable:!0});import{maskSensitiveOutput as u}from"@fjall/util";async function p(n,o,e,t){if(e.length===0)return;const r=await n.hashService.pruneStateEntries(o,e);if(!r.success){t.onLog?.(`Could not prune stale state entries (${e.join(", ")}): ${u(r.error.message)}`,"warn");return}r.data&&t.onLog?.(`Pruned stale state entries for removed stacks no longer in CloudFormation: ${e.join(", ")}`,"info")}i(p,"persistStaleStatePrune");export{p as persistStaleStatePrune};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var h=Object.defineProperty;var i=(r,n)=>h(r,"name",{value:n,configurable:!0});import{mkdir as D,readFile as S,rename as y,rm as w,writeFile as b}from"node:fs/promises";import{dirname as J,join as N}from"node:path";import{fjallStatePath as k,getErrorMessage as d,maskSensitiveOutput as s}from"@fjall/util";import{logger as a}from"@fjall/util/logger";import{DriftJournalRecordSchema as v}from"../../types/drift.js";const c="DriftJournal";function p(){return k("drift")}i(p,"defaultDriftJournalDir");const A=/^[A-Za-z0-9][A-Za-z0-9-]*$/;function g(r,n,e,o){for(const[u,t]of[["accountId",n],["region",e],["stackName",o]])if(!A.test(t))return a.debug(c,"Refusing drift journal path for unsafe key segment",{label:u,value:s(t)}),null;return N(r,n,e,`${o}.json`)}i(g,"journalPath");async function I(r,n){const e=n?.journalDir??p(),o=g(e,r.accountId,r.region,r.stackName);if(o===null)return;const u={version:1,accountId:r.accountId,region:r.region,stackName:r.stackName,recordedAt:r.recordedAt,suspects:[...r.suspects]};try{await D(J(o),{recursive:!0});const t=`${o}.tmp-${process.pid}`;await b(t,JSON.stringify(u,null,2),"utf8"),await y(t,o)}catch(t){a.debug(c,"Failed to record drift suspects",{stackName:r.stackName,error:s(d(t))})}}i(I,"recordDriftSuspects");async function R(r,n,e,o){const u=o?.journalDir??p(),t=g(u,r,n,e);if(t===null)return null;let l;try{l=await S(t,"utf8")}catch{return null}let m;try{m=JSON.parse(l)}catch(j){return a.debug(c,"Corrupt drift journal ignored",{stackName:e,error:s(d(j))}),null}const f=v.safeParse(m);return f.success?f.data:(a.debug(c,"Schema-drifted drift journal ignored",{stackName:e,error:f.error.message}),null)}i(R,"readDriftSuspects");async function z(r,n,e,o){const u=o?.journalDir??p(),t=g(u,r,n,e);t!==null&&await w(t,{force:!0}).catch(l=>{a.debug(c,"Failed to clear drift journal",{stackName:e,error:s(d(l))})})}i(z,"clearDriftSuspects");export{z as clearDriftSuspects,p as defaultDriftJournalDir,R as readDriftSuspects,I as recordDriftSuspects};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var O=Object.defineProperty;var g=(
|
|
1
|
+
var O=Object.defineProperty;var g=(d,o)=>O(d,"name",{value:o,configurable:!0});import{CloudFormationClient as D,DescribeStackEventsCommand as B,DescribeStacksCommand as K,GetTemplateCommand as _}from"@aws-sdk/client-cloudformation";import{mkdir as x,rename as I,writeFile as J}from"node:fs/promises";import{join as $}from"node:path";import{success as M,failure as l}from"@fjall/generator";import{fjallStatePath as R,getErrorMessage as A,maskSensitiveOutput as S}from"@fjall/util";import{composeSdkAbortSignal as k,isAborted as G}from"../../aws/organisations/types.js";const b=50,U=20;function V(){return R("forensics","remediation")}g(V,"defaultRemediationForensicsDir");async function Q(d,o,w){const{stackName:t,opId:v,abortSignal:i}=o,m=d.getClient(D);let f,h,y,E,T,u,N;try{const e=await m.send(new _({StackName:t,TemplateStage:"Original"}),{abortSignal:k(i)});if(e.TemplateBody===void 0||e.TemplateBody==="")return l(new Error(`Forensics capture aborted the remediation of ${t}: GetTemplate returned an empty template body \u2014 nothing was mutated.`));f=e.TemplateBody;const n=(await m.send(new K({StackName:t}),{abortSignal:k(i)})).Stacks?.[0];if(n===void 0)return l(new Error(`Forensics capture aborted the remediation of ${t}: DescribeStacks returned no stack \u2014 nothing was mutated.`));h=n,y=n.Capabilities??[],E=(n.Parameters??[]).flatMap(r=>r.ParameterKey!==void 0?[r.ParameterKey]:[]),T=n.StackStatus??"UNKNOWN";const c=[];let a,P=0;do{if(G(i))throw new Error(`aborted while reading the stack events of ${t}`);const r=await m.send(new B({StackName:t,...a!==void 0?{NextToken:a}:{}}),{abortSignal:k(i)});c.push(...r.StackEvents??[]),a=r.NextToken,P+=1}while(a!==void 0&&c.length<b&&P<U);N=a!==void 0||c.length>b,u=c.slice(0,b)}catch(e){return l(new Error(`Forensics capture failed for ${t} \u2014 aborting before any mutation: ${S(A(e))}`))}const F=w?.forensicsDir??V(),j=(w?.now?.()??new Date).toISOString(),s=$(F,v,j.replace(/[^0-9A-Za-z.-]/g,"")),C=[{name:"live-template.json",content:f},{name:"stack-description.json",content:JSON.stringify(h,null,2)},{name:"stack-events.json",content:JSON.stringify(u,null,2)},{name:"drift-findings.json",content:JSON.stringify(o.findings,null,2)},{name:"manifest.json",content:JSON.stringify({opId:v,stackName:t,capturedAt:j,files:["live-template.json","stack-description.json","stack-events.json","drift-findings.json"],...N?{eventsTruncated:!0,eventsCaptured:u.length}:{}},null,2)}];try{await x(s,{recursive:!0});for(const e of C){const p=$(s,e.name),n=`${p}.tmp-${process.pid}`;await J(n,S(e.content),"utf8"),await I(n,p)}}catch(e){return l(new Error(`Forensics capture could not persist the evidence bundle for ${t} at ${s} \u2014 aborting before any mutation: ${S(A(e))}`))}return M({forensicsPath:s,files:C.map(e=>e.name),liveTemplateBody:f,capabilities:y,parameterKeys:E,stackStatus:T})}g(Q,"captureRemediationForensics");export{Q as captureRemediationForensics,V as defaultRemediationForensicsDir};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var b=Object.defineProperty;var c=(e,r)=>b(e,"name",{value:r,configurable:!0});import{createHash as S}from"node:crypto";import{mkdir as T,readdir as $,readFile as g,rename as E,rm as F,rmdir as O,writeFile as _}from"node:fs/promises";import{
|
|
2
|
-
`),n.update(t);return n.digest("hex").slice(0,16)}c(
|
|
1
|
+
var b=Object.defineProperty;var c=(e,r)=>b(e,"name",{value:r,configurable:!0});import{createHash as S}from"node:crypto";import{mkdir as T,readdir as $,readFile as g,rename as E,rm as F,rmdir as O,writeFile as _}from"node:fs/promises";import{basename as I,dirname as J,join as w}from"node:path";import{success as h,failure as D}from"@fjall/generator";import{fjallStatePath as k,getErrorMessage as p,maskSensitiveOutput as l}from"@fjall/util";import{logger as d}from"@fjall/util/logger";import{RemediationJournalRecordSchema as C}from"../../types/remediation.js";const f="RemediationJournal",M=90;function y(){return k("state","remediation")}c(y,"defaultRemediationJournalDir");function Q(e,r){const n=S("sha256");n.update(e);for(const t of[...r].sort())n.update(`
|
|
2
|
+
`),n.update(t);return n.digest("hex").slice(0,16)}c(Q,"computeRemediationOpId");function X(e){return`${K(e.verb)} operation ${e.opId} already completed`}c(X,"formatCompletedOperationNotice");function P(e){return`Archived completed ${e.verb} operation ${e.opId} from a previous incarnation \u2014 starting a fresh operation`}c(P,"formatArchivedOperationNotice");function K(e){return e.charAt(0).toUpperCase()+e.slice(1)}c(K,"capitaliseVerb");const R=/^[A-Za-z0-9][A-Za-z0-9-]*$/,v=/^[a-f0-9]{8,64}$/;function A(e,r,n,t,a){for(const[o,u,i]of[["accountId",r,R],["region",n,R],["stackName",t,R],["opId",a,v]])if(!i.test(u))return D(new Error(`Refusing remediation journal path: unsafe ${o} segment "${l(u)}"`));return h(w(e,r,n,t,`${a}.json`))}c(A,"journalPath");async function ee(e,r){const n=r?.journalDir??y(),t=A(n,e.accountId,e.region,e.stackName,e.opId);if(!t.success)return t;try{await T(J(t.data),{recursive:!0});const a=`${t.data}.tmp-${process.pid}`;return await _(a,JSON.stringify(e,null,2),"utf8"),await E(a,t.data),h(t.data)}catch(a){return D(new Error(`Failed to write remediation journal for ${e.stackName}/${e.opId}: ${l(p(a))}`))}}c(ee,"writeRemediationJournal");async function te(e,r,n,t,a){const o=a?.journalDir??y(),u=A(o,e,r,n,t);if(!u.success)return null;let i;try{i=await g(u.data,"utf8")}catch{return null}return x(i,`${n}/${t}`)}c(te,"readRemediationJournal");function x(e,r){let n;try{n=JSON.parse(e)}catch(a){return d.warn(f,"Corrupt remediation journal ignored",{label:r,error:l(p(a))}),null}const t=C.safeParse(n);return t.success?t.data:(d.warn(f,"Schema-drifted remediation journal ignored",{label:r,error:l(t.error.message)}),null)}c(x,"parseJournal");async function re(e,r){const n=r?.journalDir??y(),t=A(n,e.accountId,e.region,e.stackName,e.opId);if(!t.success)return t;const a=(e.completedAt??e.updatedAt).replace(/[^0-9A-Za-z.-]/g,""),o=t.data.replace(/\.json$/,()=>`.done-${a}.json`);try{return await E(t.data,o),h(o)}catch(u){return D(new Error(`Failed to archive the completed remediation journal for ${e.stackName}/${e.opId}: ${l(p(u))}`))}}c(re,"archiveCompletedRemediationJournal");async function ne(e,r,n){if(!v.test(e))return h(null);const t=n?.journalDir??y();let a;try{a=await N(t,3)}catch{return h(null)}const o=[];for(const i of a.filter(s=>s.endsWith(`/${e}.json`))){let s;try{s=await g(i,"utf8")}catch{continue}const m=x(s,e);m!==null&&o.push(m)}if(o.length===0)return h(null);if(o.length===1)return h(o[0]??null);const u=o.find(i=>i.accountId===r?.accountId&&i.region===r?.region);return u!==void 0?h(u):D(new Error(`Remediation operation ${e} exists in more than one context: ${o.map(i=>`${i.accountId}/${i.region}`).join(", ")}${r!==void 0?` \u2014 none matches the current credentials (${r.accountId}/${r.region}). Switch to the matching account/region and re-run 'fjall drift resume ${e}'.`:"."}`))}c(ne,"findRemediationJournalByOpId");async function z(e,r,n,t){const a=t?.journalDir??y(),o=w(a,e,r,n);if(!R.test(n))return[];let u;try{u=await $(o)}catch{return[]}const i=[];for(const s of u){if(!s.endsWith(".json"))continue;let m;try{m=await g(w(o,s),"utf8")}catch{continue}const j=x(m,`${n}/${s}`);j!==null&&j.phase!=="done"&&i.push(j)}return i}c(z,"listActiveRemediationOps");async function ae(e,r,n){const t=n?.journalDir??y();let a;try{a=await $(w(t,e,r))}catch{return[]}const o=[];for(const u of a)o.push(...await z(e,r,u,n));return o}c(ae,"listActiveRemediationOpsForContext");async function oe(e,r=()=>new Date){const n=e?.journalDir??y(),t=r().getTime()-M*24*60*60*1e3;let a;try{a=await N(n,3)}catch{return}for(const o of a){let u;try{u=await g(o,"utf8")}catch{continue}const i=x(u,o);if(i===null||i.phase!=="done")continue;const s=Date.parse(i.completedAt??i.updatedAt);if(Number.isNaN(s)||s>=t)continue;let m=!0;await F(o,{force:!0}).catch(j=>{m=!1,d.debug(f,"Failed to sweep expired remediation journal",{file:o,error:l(p(j))})}),!(!m||i.forensicsPath===void 0)&&await B(i.forensicsPath,i.opId)}}c(oe,"sweepExpiredRemediationJournals");async function B(e,r){const n=J(e),t=I(e)===r;if(!v.test(r)||!t&&I(n)!==r){d.debug(f,"Kept forensics bundle \u2014 unexpected path shape",{forensicsPath:e});return}if(t){await G(e);return}let a=!0;await F(e,{recursive:!0,force:!0}).catch(o=>{a=!1,d.warn(f,"Failed to sweep the expired journal's forensics",{forensicsPath:e,error:l(p(o))})}),a&&await O(n).catch(o=>{d.debug(f,"Kept forensics opId directory",{opDir:n,error:l(p(o))})})}c(B,"sweepForensicsBundle");async function G(e){let r;try{r=await $(e,{withFileTypes:!0})}catch(t){d.warn(f,"Failed to sweep the expired journal's forensics",{forensicsPath:e,error:l(p(t))});return}let n=!1;for(const t of r){if(!t.isFile()){n=!0;continue}await F(w(e,t.name),{force:!0}).catch(a=>{n=!0,d.warn(f,"Failed to sweep the expired journal's forensics",{forensicsPath:w(e,t.name),error:l(p(a))})})}if(n){d.debug(f,"Kept flat forensics directory \u2014 nested bundles live",{forensicsPath:e});return}await O(e).catch(t=>{d.debug(f,"Kept forensics opId directory",{forensicsPath:e,error:l(p(t))})})}c(G,"sweepFlatBundleFiles");async function N(e,r){const n=await $(e,{withFileTypes:!0}),t=[];for(const a of n){const o=w(e,a.name);a.isFile()&&a.name.endsWith(".json")?t.push(o):a.isDirectory()&&r>0&&t.push(...await N(o,r-1).catch(()=>[]))}return t}c(N,"listFilesRecursive");export{M as REMEDIATION_JOURNAL_RETENTION_DAYS,re as archiveCompletedRemediationJournal,Q as computeRemediationOpId,y as defaultRemediationJournalDir,ne as findRemediationJournalByOpId,P as formatArchivedOperationNotice,X as formatCompletedOperationNotice,z as listActiveRemediationOps,ae as listActiveRemediationOpsForContext,te as readRemediationJournal,oe as sweepExpiredRemediationJournals,ee as writeRemediationJournal};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var g=Object.defineProperty;var
|
|
1
|
+
var g=Object.defineProperty;var f=(y,e)=>g(y,"name",{value:e,configurable:!0});import{mkdir as p,readFile as w,rename as S,writeFile as b}from"node:fs/promises";import{join as l}from"node:path";import{CloudFormationClient as C,DescribeTypeCommand as D}from"@aws-sdk/client-cloudformation";import{z as a}from"zod";import{success as c,failure as h}from"@fjall/generator";import{fjallStatePath as O,getErrorMessage as u,maskSensitiveOutput as n}from"@fjall/util";import{logger as o}from"@fjall/util/logger";import{composeSdkAbortSignal as P}from"../../aws/organisations/types.js";import{CFN_REGISTRY_SEED as T}from"./cfnRegistrySeed.js";const d="CfnRegistryService",E=a.object({createOnlyProperties:a.array(a.string()).optional(),conditionalCreateOnlyProperties:a.array(a.string()).optional(),primaryIdentifier:a.array(a.string()).optional()}),R=a.object({typeName:a.string(),createOnlyProperties:a.array(a.string()),conditionalCreateOnlyProperties:a.array(a.string()),primaryIdentifier:a.array(a.string())}).strict(),v=a.object({cachedAt:a.string(),registryLastUpdated:a.string().optional(),summary:R}).strict(),$=10080*60*1e3;class G{static{f(this,"CfnRegistryService")}aws;cacheDir;cacheTtlMs;seed;now;memory=new Map;constructor(e,t){this.aws=e,this.cacheDir=t?.cacheDir??O("cache","cfn-registry"),this.cacheTtlMs=t?.cacheTtlMs??$,this.seed=t?.seed??T,this.now=t?.now??(()=>Date.now())}async getSchemaSummary(e,t){const s=this.memory.get(e);if(s!==void 0)return c(s);const r=await this.readCache(e);if(r!==void 0&&this.isFresh(r))return this.memory.set(e,r.summary),c(r.summary);const i=await this.describeType(e,t);if(i.success)return this.memory.set(e,i.data.summary),await this.writeCache(e,i.data),c(i.data.summary);if(r!==void 0)return o.warn(d,"Live DescribeType failed \u2014 serving stale cache",{typeName:e,error:n(i.error.message)}),this.memory.set(e,r.summary),c(r.summary);const m=this.seed[e];return m!==void 0?(o.warn(d,"Live DescribeType failed \u2014 serving baked seed",{typeName:e,error:n(i.error.message)}),this.memory.set(e,m),c(m)):h(i.error)}isFresh(e){const t=Date.parse(e.cachedAt);return Number.isNaN(t)?!1:this.now()-t<=this.cacheTtlMs}cachePath(e){const t=`${e.replaceAll("::","-")}.json`;return l(this.cacheDir,this.aws.getRegion(),t)}async describeType(e,t){try{const r=await this.aws.getClient(C).send(new D({Type:"RESOURCE",TypeName:e}),{abortSignal:P(t)});if(r.Schema===void 0||r.Schema==="")return h(new Error(`DescribeType returned no schema for ${e}`));const i=E.safeParse(JSON.parse(r.Schema));return i.success?c({cachedAt:new Date(this.now()).toISOString(),...r.LastUpdated!==void 0?{registryLastUpdated:r.LastUpdated.toISOString()}:{},summary:{typeName:e,createOnlyProperties:i.data.createOnlyProperties??[],conditionalCreateOnlyProperties:i.data.conditionalCreateOnlyProperties??[],primaryIdentifier:i.data.primaryIdentifier??[]}}):h(new Error(`Unparsable registry schema for ${e}: ${n(i.error.message)}`))}catch(s){return h(new Error(`DescribeType failed for ${e}: ${n(u(s))}`))}}async readCache(e){const t=this.cachePath(e);try{const s=await w(t,"utf8"),r=v.safeParse(JSON.parse(s));if(!r.success){o.debug(d,"Discarding unparsable registry cache entry",{path:t,error:n(r.error.message)});return}return r.data}catch(s){o.debug(d,"Registry cache read missed",{path:t,error:n(u(s))});return}}async writeCache(e,t){const s=this.cachePath(e);try{await p(l(this.cacheDir,this.aws.getRegion()),{recursive:!0});const r=`${s}.tmp-${process.pid}`;await b(r,JSON.stringify(t,null,2),"utf8"),await S(r,s)}catch(r){o.debug(d,"Registry cache write failed",{path:s,error:n(u(r))})}}}export{G as CfnRegistryService};
|
|
@@ -58,6 +58,21 @@ export declare class TemplateHashService {
|
|
|
58
58
|
* Update state file with new hashes after successful deployment
|
|
59
59
|
*/
|
|
60
60
|
updateStateAfterDeploy(appPath: string, deployedStacks: Map<string, string>, stackStatuses?: Map<string, string>): Promise<Result<void, TemplateHashError>>;
|
|
61
|
+
/**
|
|
62
|
+
* Remove state entries for stacks the caller has verified are BOTH no
|
|
63
|
+
* longer synthesised and absent from CloudFormation. Self-heals the
|
|
64
|
+
* phantom-drift class: a state-only entry for a stack that no longer
|
|
65
|
+
* exists anywhere reports "changed" on every comparison forever, so the
|
|
66
|
+
* plan gate can never converge to "no changes" after a stack removal.
|
|
67
|
+
* Entries for removed stacks that still exist in CFN must NOT be pruned —
|
|
68
|
+
* their "changed" flag is the genuine removed-stack signal the
|
|
69
|
+
* destruction gate consumes.
|
|
70
|
+
*
|
|
71
|
+
* Returns true only when a prune was actually written; false means there
|
|
72
|
+
* was nothing to remove (no state file, or no matching entries) so
|
|
73
|
+
* callers never report a heal that did not land.
|
|
74
|
+
*/
|
|
75
|
+
pruneStateEntries(appPath: string, stackNames: readonly string[]): Promise<Result<boolean, TemplateHashError>>;
|
|
61
76
|
/**
|
|
62
77
|
* Compare a single cascade account's synthesised stack against its
|
|
63
78
|
* per-account state file. Returns `changed: true` on ANY read/hash failure so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var T=Object.defineProperty;var d=(
|
|
1
|
+
var T=Object.defineProperty;var d=(u,e)=>T(u,"name",{value:e,configurable:!0});import{createHash as E}from"crypto";import{readFile as F,readdir as $}from"fs/promises";import{join as y,basename as k}from"path";import{fileExists as A}from"@fjall/util/fsHelpers";import{success as i,failure as l}from"@fjall/generator";import{BaseServiceError as N}from"../../types/errors/ServiceError.js";import{readStateFile as m,writeStateFile as S,readStateFileAt as H,writeStateFileAt as x,createEmptyState as C,updateTemplateHash as _,getStateFilePath as p}from"../../types/config/FjallState.js";class f extends N{static{d(this,"TemplateHashError")}errorType;constructor(e,t,r,a=!1){super(`TEMPLATE_HASH_${t.toUpperCase()}`,e,r,a),this.errorType=t}}const w=".template.json";class R{static{d(this,"TemplateHashService")}async computeTemplateHash(e){try{const t=await F(e,"utf-8"),r=JSON.stringify(JSON.parse(t)),a=E("sha256").update(r).digest("hex");return i(a)}catch(t){return l(new f(`Failed to hash template: ${e}`,"hash_failed",{path:e,error:t}))}}async getTemplateHashes(e){if(!await A(e))return l(new f(`CDK output directory not found: ${e}`,"cdk_out_not_found",{path:e}));try{const r=(await $(e)).filter(s=>s.endsWith(w)),a=new Map;for(const s of r){const n=k(s,w),o=y(e,s),c=await this.computeTemplateHash(o);if(!c.success)return l(c.error);a.set(n,c.data)}return i(a)}catch(t){return l(new f(`Failed to read CDK output directory: ${e}`,"read_failed",{path:e,error:t}))}}async compareWithState(e,t){const r=await m(t),a=new Map;let s=0,n=0;for(const[o,c]of e){const h=r?.templateHashes[o],g=!h||h.hash!==c;a.set(o,g),g?s++:n++}if(r?.templateHashes)for(const o of Object.keys(r.templateHashes))e.has(o)||(a.set(o,!0),s++);return i({stackChanges:a,currentHashes:e,changedCount:s,unchangedCount:n})}async updateStateAfterDeploy(e,t,r){try{let a=await m(e)??C();for(const[s,n]of t){const o=r?.get(s);a=_(a,s,n,o)}return await S(e,a),i(void 0)}catch(a){return l(new f(`Failed to write state file: ${p(e)}`,"state_write_failed",{appPath:e,error:a}))}}async pruneStateEntries(e,t){if(t.length===0)return i(!1);try{const r=await m(e);if(!r)return i(!1);const a={...r.templateHashes};let s=!1;for(const n of t)n in a&&(delete a[n],s=!0);return s?(await S(e,{...r,templateHashes:a}),i(!0)):i(!1)}catch(r){return l(new f(`Failed to write state file: ${p(e)}`,"state_write_failed",{appPath:e,error:r}))}}async compareCascadeStack(e,t,r){const a=y(e,`${t}${w}`),s=await this.computeTemplateHash(a);if(!s.success)return{changed:!0,currentHash:void 0};const n=s.data,c=(await H(r))?.templateHashes[t];return{changed:!c||c.hash!==n,currentHash:n}}async persistCascadeStack(e,t,r,a){try{const s=await H(e)??C(),n=_(s,t,r,a);return await x(e,n),i(void 0)}catch(s){return l(new f(`Failed to write cascade state file: ${e}`,"state_write_failed",{stateFilePath:e,error:s}))}}getStateFilePath(e){return p(e)}stackHasChanges(e,t){return e.stackChanges.get(t)??!0}getChangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>t).map(([t])=>t)}getUnchangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>!t).map(([t])=>t)}}export{f as TemplateHashError,R as TemplateHashService};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
1
|
+
var I=Object.defineProperty;var a=(t,e)=>I(t,"name",{value:e,configurable:!0});import{existsSync as p,mkdirSync as H,readdirSync as h,readFileSync as S,unlinkSync as D}from"fs";import{createHash as A}from"crypto";import{basename as F,dirname as g,join as s}from"path";import{z as u}from"zod";import{success as c,failure as f}from"@fjall/generator";import{getDomainStackName as k,getErrorMessage as w,normaliseError as m}from"@fjall/util";import{ATOMIC_TMP_SUFFIX as x}from"@fjall/util/atomicTempNaming";import{atomicWrite as T}from"@fjall/util/fsHelpers";import{FjallStateFileSchema as _,getStateFilePath as y}from"./FjallState.js";const l=".deploy-state.json",b=u.object({lastDeployedAt:u.string(),cloudformationStackName:u.string(),hostedZoneId:u.string().optional(),directoryHash:u.string().optional()}).strict();function E(t,e){if(!p(t))return c(void 0);let r;try{r=S(t,"utf8")}catch(i){return f(m(i))}let n;try{n=JSON.parse(r)}catch{return c(void 0)}return c(e(n))}a(E,"readStateDocument");function j(t){const e=_.safeParse(t);if(!e.success)return;const{domain:r,lastDeployedAt:n}=e.data;if(!(r===void 0||n===void 0))return{lastDeployedAt:n,cloudformationStackName:r.cloudformationStackName,...r.hostedZoneId!==void 0?{hostedZoneId:r.hostedZoneId}:{},...r.directoryHash!==void 0?{directoryHash:r.directoryHash}:{}}}a(j,"parseCurrentDocument");function L(t){const e=b.safeParse(t);return e.success?e.data:void 0}a(L,"parseLegacyDocument");function K(t){const e=E(y(t),j);if(!e.success)return e;const r=E(s(t,l),L);if(!r.success)return e.data!==void 0?e:r;if(e.data===void 0)return r;if(r.data===void 0)return e;const n=Date.parse(e.data.lastDeployedAt),i=Date.parse(r.data.lastDeployedAt);return Number.isFinite(i)&&(!Number.isFinite(n)||i>n)?r:e}a(K,"readDomainState");function Z(t){for(const e of[l,`${l}.tmp`])try{D(s(t,e))}catch{}}a(Z,"unlinkLegacyStateFiles");function C(t,e){const r=y(t),n={version:1,lastDeployedAt:e.lastDeployedAt,templateHashes:{},domain:{cloudformationStackName:e.cloudformationStackName,...e.hostedZoneId!==void 0?{hostedZoneId:e.hostedZoneId}:{},...e.directoryHash!==void 0?{directoryHash:e.directoryHash}:{}}};try{H(g(r),{recursive:!0}),T(r,JSON.stringify(n,null,2)+`
|
|
2
|
+
`)}catch(i){return f(m(i))}return Z(t),c(void 0)}a(C,"writeDomainState");const O=new Set(["node_modules","cdk.out",".git"]),$=[".fjall-state",".deploy-state"];function M(t){return O.has(t)||$.some(e=>t.startsWith(e))}a(M,"isHashExcluded");function R(t){if(!p(t))return f(new Error(`domain change detection: directory not found at ${t}`));const e=A("sha256"),r=a(n=>{const i=h(s(t,n),{withFileTypes:!0}).filter(o=>!M(o.name)).sort((o,d)=>o.name.localeCompare(d.name));for(const o of i){const d=n===""?o.name:s(n,o.name);o.isDirectory()?r(d):o.isFile()&&(e.update(d),e.update("\0"),e.update(S(s(t,d))),e.update("\0"))}},"walk");try{r("")}catch(n){return f(m(n))}return c(e.digest("hex"))}a(R,"hashDomainDirectory");function P(t){const e=R(t.componentPath),r={lastDeployedAt:new Date().toISOString(),cloudformationStackName:k(t.domainName),...e.success?{directoryHash:e.data}:{}},n=C(t.componentPath,r);return n.success?c(r):n}a(P,"recordDomainDeployState");function N(t){const e=g(t),r=`${F(t)}.`;try{return h(e).filter(n=>n.startsWith(r)&&n.endsWith(x)).map(n=>s(e,n))}catch{return[]}}a(N,"atomicTempSiblings");function Q(t){const e=y(t),r=[e,...N(e),s(t,l),...N(s(t,l))],n=[];for(const i of r)try{D(i)}catch(o){(typeof o=="object"&&o!==null&&"code"in o?o.code:void 0)!=="ENOENT"&&n.push(`${i}: ${w(o)}`)}return n.length>0?f(new Error(`could not delete domain deploy state: ${n.join("; ")}`)):c(void 0)}a(Q,"deleteDomainState");export{l as LEGACY_DOMAIN_STATE_FILENAME,Q as deleteDomainState,R as hashDomainDirectory,K as readDomainState,P as recordDomainDeployState,C as writeDomainState};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var u=Object.defineProperty;var n=(t,e)=>u(t,"name",{value:e,configurable:!0});import{z as r}from"zod";import{readFile as d,unlink as f,mkdir as m}from"fs/promises";import{dirname as S,join as c}from"path";import{atomicWriteAsync as g,fileExists as h}from"@fjall/util/fsHelpers";import{logger as i}from"@fjall/util/logger";import{getErrorMessage as l,maskSensitiveOutput as y}from"@fjall/util";const F=r.object({hash:r.string(),deployedAt:r.string(),stackStatus:r.string().optional()}).strict(),j=r.object({cloudformationStackName:r.string(),hostedZoneId:r.string().optional(),directoryHash:r.string().optional()}).strict(),x=r.object({version:r.literal(1),lastDeployedAt:r.string().optional(),templateHashes:r.record(r.string(),F),domain:j.optional(),metadata:r.record(r.string(),r.unknown()).optional()}).strict(),w=".fjall-state.json";function s(t){return c(t,w)}n(s,"getStateFilePath");function E(t){return t.replace(/-/g,"")}n(E,"regionSuffix");function I(t,e,a){return c(t,`.fjall-state.cascade-${e}-${E(a)}.json`)}n(I,"getCascadeStateFilePath");async function A(t){if(!await h(t))return null;try{const e=await d(t,"utf-8"),a=JSON.parse(e),o=x.safeParse(a);return o.success?o.data:(i.debug("FjallState","State file failed schema validation",{path:t,issues:o.error.issues.map(p=>p.message)}),null)}catch(e){return i.debug("FjallState","Failed to read state file",{path:t,error:l(e)}),null}}n(A,"readStateFileAt");async function b(t,e){await m(S(t),{recursive:!0}),await g(t,JSON.stringify(e,null,2),{onCleanupError:n(a=>{i.debug("FjallState","Temp file cleanup failed (non-fatal)",{path:t,error:l(a)})},"onCleanupError")})}n(b,"writeStateFileAt");async function C(t){return A(s(t))}n(C,"readStateFile");async function J(t,e){return b(s(t),e)}n(J,"writeStateFile");function M(){return{version:1,templateHashes:{}}}n(M,"createEmptyState");async function _(t){const e=s(t);try{await f(e)}catch(a){(typeof a=="object"&&a!==null&&"code"in a?a.code:void 0)!=="ENOENT"&&i.warn("FjallState","Failed to delete state file",{path:e,error:y(l(a))})}}n(_,"deleteStateFile");function $(t,e,a,o){return{...t,lastDeployedAt:new Date().toISOString(),templateHashes:{...t.templateHashes,[e]:{hash:a,deployedAt:new Date().toISOString(),...o!==void 0&&{stackStatus:o}}}}}n($,"updateTemplateHash");export{x as FjallStateFileSchema,M as createEmptyState,_ as deleteStateFile,I as getCascadeStateFilePath,s as getStateFilePath,C as readStateFile,A as readStateFileAt,E as regionSuffix,$ as updateTemplateHash,J as writeStateFile,b as writeStateFileAt};
|
|
@@ -17,6 +17,12 @@ export interface DetectionResult {
|
|
|
17
17
|
hasDockerfile: boolean;
|
|
18
18
|
/** SSM parameter paths required by the application, parsed from the CDK manifest. */
|
|
19
19
|
requiredSecrets: string[];
|
|
20
|
+
/**
|
|
21
|
+
* State-only stacks verified absent from CloudFormation (positive
|
|
22
|
+
* DOES_NOT_EXIST). Detection converges them in-memory only; callers on
|
|
23
|
+
* an apply-authorised path persist the heal via persistStaleStatePrune.
|
|
24
|
+
*/
|
|
25
|
+
staleStateEntries: string[];
|
|
20
26
|
/** Name of the framework builder that resolved this app (e.g. "opennext", "docker"). */
|
|
21
27
|
builderName?: string;
|
|
22
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/deploy-core",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.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": "^21.
|
|
100
|
-
"@fjall/util": "^21.
|
|
99
|
+
"@fjall/generator": "^21.1.0",
|
|
100
|
+
"@fjall/util": "^21.1.0",
|
|
101
101
|
"@smithy/node-http-handler": "^4.9.13",
|
|
102
102
|
"aws-cdk": "^2.1134.0",
|
|
103
103
|
"tsx": "^4.23.1",
|