@fjall/deploy-core 0.94.1 → 0.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.minified +1 -1
- package/dist/src/aws/organisations/accounts.js +1 -99
- package/dist/src/aws/organisations/backup.js +1 -30
- package/dist/src/aws/organisations/costAllocation.js +1 -28
- package/dist/src/aws/organisations/delegatedAdmin.js +3 -43
- package/dist/src/aws/organisations/identityCentre.js +1 -23
- package/dist/src/aws/organisations/ipam.js +1 -20
- package/dist/src/aws/organisations/organisation.js +1 -103
- package/dist/src/aws/organisations/organisationalUnits.js +1 -239
- package/dist/src/aws/organisations/policies.js +1 -37
- package/dist/src/aws/organisations/ram.js +1 -19
- package/dist/src/aws/organisations/serviceAccess.js +1 -44
- package/dist/src/aws/organisations/trustedAccess.js +1 -19
- package/dist/src/aws/utils/regions.js +1 -1
- package/dist/src/index.js +1 -65
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +1 -78
- package/dist/src/orchestration/activeDeploymentGuard.js +5 -39
- package/dist/src/orchestration/applicationDeploy.js +1 -149
- package/dist/src/orchestration/applicationDeployHelpers.js +4 -223
- package/dist/src/orchestration/applicationDestroy.js +1 -131
- package/dist/src/orchestration/builders/dockerBuilder.js +1 -98
- package/dist/src/orchestration/builders/openNextBuilder.js +1 -144
- package/dist/src/orchestration/cascadeHelpers.js +1 -160
- package/dist/src/orchestration/contextHelpers.js +1 -107
- package/dist/src/orchestration/deploy.js +1 -42
- package/dist/src/orchestration/destroy.js +1 -67
- package/dist/src/orchestration/detectionPipeline.js +1 -84
- package/dist/src/orchestration/dockerBuildHelper.js +1 -49
- package/dist/src/orchestration/dockerInterface.js +0 -1
- package/dist/src/orchestration/domainInterface.js +0 -1
- package/dist/src/orchestration/openNextBuild.js +3 -243
- package/dist/src/orchestration/organisationDeploy.js +3 -284
- package/dist/src/orchestration/organisationDestroy.js +3 -189
- package/dist/src/orchestration/organisationSetup.js +1 -247
- package/dist/src/orchestration/resolveOperation.js +1 -123
- package/dist/src/orchestration/welcomeImageHelper.js +1 -64
- package/dist/src/services/application/ApplicationStackService.js +1 -218
- package/dist/src/services/application/applicationStackHelpers.js +4 -248
- package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -244
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -125
- package/dist/src/services/infrastructure/CdkProcessManager.js +3 -278
- package/dist/src/services/infrastructure/CdkService.js +3 -213
- package/dist/src/services/infrastructure/CloudFormationService.js +1 -248
- package/dist/src/services/infrastructure/ICdkProcessManager.js +0 -1
- package/dist/src/services/supporting/CdkContextBuilder.js +1 -44
- package/dist/src/services/supporting/TemplateHashService.js +1 -152
- package/dist/src/steps/stepRegistry.js +1 -505
- package/dist/src/types/apiClient.js +0 -1
- package/dist/src/types/detection.js +0 -1
- package/dist/src/types/frameworkBuilder.js +0 -8
- package/dist/src/types/params.js +0 -1
- package/dist/src/types/patternDetection.js +1 -88
- package/dist/src/types/stepDefinitions.js +1 -98
- package/package.json +4 -4
|
@@ -1,218 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getApplicationStackName } from "../../types/operations.js";
|
|
3
|
-
import { success, failure } from "@fjall/generator";
|
|
4
|
-
import { ApplicationError } from "../../types/application/ApplicationServiceTypes.js";
|
|
5
|
-
import { logger } from "@fjall/util/logger";
|
|
6
|
-
import { convertCloudFormationOutputsToRecord } from "../supporting/helpers.js";
|
|
7
|
-
import { destroyAllStacks, mapSettledResults, resolveWebsiteUrl } from "./applicationStackHelpers.js";
|
|
8
|
-
/**
|
|
9
|
-
* Service for deploying and destroying individual CloudFormation stacks.
|
|
10
|
-
*
|
|
11
|
-
* Handles single-stack deploy/destroy, parallel operations, and the full
|
|
12
|
-
* destroy-all-stacks orchestration.
|
|
13
|
-
*
|
|
14
|
-
* Unlike the CLI version, services are constructor-injected rather than
|
|
15
|
-
* fetched via singleton factories.
|
|
16
|
-
*/
|
|
17
|
-
export class ApplicationStackService {
|
|
18
|
-
cdkService;
|
|
19
|
-
cloudFormationService;
|
|
20
|
-
aws;
|
|
21
|
-
constructor(cdkService, cloudFormationService, aws) {
|
|
22
|
-
this.cdkService = cdkService;
|
|
23
|
-
this.cloudFormationService = cloudFormationService;
|
|
24
|
-
this.aws = aws;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Delegate to CdkService.runCdkSynth — exposed for destroyAllStacks orchestration.
|
|
28
|
-
*/
|
|
29
|
-
async runCdkSynth(context) {
|
|
30
|
-
return this.cdkService.runCdkSynth(context);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Deploy a specific stack type for an application
|
|
34
|
-
*/
|
|
35
|
-
async deployStack(stackType, context, callbacks) {
|
|
36
|
-
const appName = context.target;
|
|
37
|
-
const stackName = getApplicationStackName(appName, stackType);
|
|
38
|
-
const result = await this.cdkService.runCdkDeploy(context, stackName, callbacks?.onOutput, callbacks?.onResourceProgress, this.aws);
|
|
39
|
-
if (result.success) {
|
|
40
|
-
const stepData = result.data;
|
|
41
|
-
logger.debug("ApplicationStackService", "CDK deploy result", {
|
|
42
|
-
stackName,
|
|
43
|
-
stackType,
|
|
44
|
-
success: true,
|
|
45
|
-
message: stepData.message,
|
|
46
|
-
status: stepData.status,
|
|
47
|
-
skipped: stepData.details?.skipped,
|
|
48
|
-
hasOutput: !!stepData.details?.output
|
|
49
|
-
});
|
|
50
|
-
const outputsResult = await this.cloudFormationService.getStackOutputs(stackName);
|
|
51
|
-
logger.debug("ApplicationStackService", "Stack outputs after deploy", {
|
|
52
|
-
stackName,
|
|
53
|
-
hasOutputs: outputsResult.success && outputsResult.data.length > 0,
|
|
54
|
-
outputCount: outputsResult.success ? outputsResult.data.length : 0
|
|
55
|
-
});
|
|
56
|
-
const outputs = outputsResult.success && outputsResult.data.length > 0
|
|
57
|
-
? convertCloudFormationOutputsToRecord(outputsResult.data)
|
|
58
|
-
: undefined;
|
|
59
|
-
return success({
|
|
60
|
-
stackType,
|
|
61
|
-
stackName,
|
|
62
|
-
skipped: stepData.details?.skipped === true,
|
|
63
|
-
outputs
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
const errorMsg = result.error || `Failed to deploy ${stackType} infrastructure`;
|
|
68
|
-
logger.error("ApplicationStackService", "Stack deployment failed", {
|
|
69
|
-
stackType,
|
|
70
|
-
target: context.target,
|
|
71
|
-
error: errorMsg
|
|
72
|
-
});
|
|
73
|
-
return failure(new ApplicationError(errorMsg, {
|
|
74
|
-
errorType: "deployment_failed",
|
|
75
|
-
appName: context.target,
|
|
76
|
-
operation: "deployStack",
|
|
77
|
-
stackType
|
|
78
|
-
}));
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Deploy multiple stacks in parallel within a deployment phase.
|
|
83
|
-
*
|
|
84
|
-
* Uses Promise.allSettled to ensure all stacks are attempted even if some fail.
|
|
85
|
-
*/
|
|
86
|
-
async deployStacksInParallel(stacks, context, callbacks) {
|
|
87
|
-
const onOutputCallback = callbacks?.onOutput;
|
|
88
|
-
const onResourceProgressCallback = callbacks?.onResourceProgress;
|
|
89
|
-
const onStackCompleteCallback = callbacks?.onStackComplete;
|
|
90
|
-
const results = await Promise.allSettled(stacks.map(async (stack) => {
|
|
91
|
-
const startTime = Date.now();
|
|
92
|
-
const result = await this.deployStack(stack, context, {
|
|
93
|
-
onOutput: onOutputCallback
|
|
94
|
-
? (chunk) => onOutputCallback(chunk, stack)
|
|
95
|
-
: undefined,
|
|
96
|
-
onResourceProgress: onResourceProgressCallback
|
|
97
|
-
? (event) => onResourceProgressCallback(event, stack)
|
|
98
|
-
: undefined
|
|
99
|
-
});
|
|
100
|
-
const duration = Date.now() - startTime;
|
|
101
|
-
const deploymentResult = {
|
|
102
|
-
stack,
|
|
103
|
-
success: result.success,
|
|
104
|
-
error: result.success ? undefined : result.error,
|
|
105
|
-
duration,
|
|
106
|
-
...(result.success && result.data.outputs !== undefined
|
|
107
|
-
? { outputs: result.data.outputs }
|
|
108
|
-
: {})
|
|
109
|
-
};
|
|
110
|
-
if (!result.success && result.error) {
|
|
111
|
-
logger.debug("deployStacksInParallel", `Stack ${stack} failed`, {
|
|
112
|
-
error: result.error.message
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
onStackCompleteCallback?.(stack, result.success, duration, result.success ? undefined : result.error);
|
|
116
|
-
return deploymentResult;
|
|
117
|
-
}));
|
|
118
|
-
return success(mapSettledResults(results, stacks));
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Destroy a specific stack type
|
|
122
|
-
*/
|
|
123
|
-
async destroyStack(stackType, context, callbacks, useCdkOut) {
|
|
124
|
-
const appName = context.target;
|
|
125
|
-
const stackName = getApplicationStackName(appName, stackType);
|
|
126
|
-
const result = await this.cdkService.runCdkDestroy(context, stackName, callbacks?.onOutput, callbacks?.onResourceProgress, this.aws, useCdkOut);
|
|
127
|
-
if (result.success) {
|
|
128
|
-
return success({
|
|
129
|
-
stackType,
|
|
130
|
-
stackName,
|
|
131
|
-
skipped: false,
|
|
132
|
-
outputs: undefined
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
return failure(new ApplicationError(result.error || `Failed to destroy ${stackType} stack`, {
|
|
137
|
-
errorType: "destroy_failed",
|
|
138
|
-
appName: context.target,
|
|
139
|
-
operation: "destroyStack",
|
|
140
|
-
stackType
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Destroy multiple stacks in parallel within a destroy phase.
|
|
146
|
-
*
|
|
147
|
-
* IMPORTANT: For parallel operations, useCdkOut must be true and CDK synth must
|
|
148
|
-
* be run beforehand. Multiple CDK commands cannot run in parallel because they
|
|
149
|
-
* all try to synthesise to the same cdk.out directory.
|
|
150
|
-
*/
|
|
151
|
-
async destroyStacksInParallel(stacks, context, callbacks, useCdkOut) {
|
|
152
|
-
const onOutputCallback = callbacks?.onOutput;
|
|
153
|
-
const onResourceProgressCallback = callbacks?.onResourceProgress;
|
|
154
|
-
const onStackCompleteCallback = callbacks?.onStackComplete;
|
|
155
|
-
const results = await Promise.allSettled(stacks.map(async (stack) => {
|
|
156
|
-
const startTime = Date.now();
|
|
157
|
-
const result = await this.destroyStack(stack, context, {
|
|
158
|
-
onOutput: onOutputCallback
|
|
159
|
-
? (chunk) => onOutputCallback(chunk, stack)
|
|
160
|
-
: undefined,
|
|
161
|
-
onResourceProgress: onResourceProgressCallback
|
|
162
|
-
? (event) => onResourceProgressCallback(event, stack)
|
|
163
|
-
: undefined
|
|
164
|
-
}, useCdkOut);
|
|
165
|
-
const duration = Date.now() - startTime;
|
|
166
|
-
const errorMessage = result.success
|
|
167
|
-
? ""
|
|
168
|
-
: (result.error?.message ?? "");
|
|
169
|
-
const isAlreadyDeleted = errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
|
|
170
|
-
errorMessage.includes(CDK_NO_STACKS_MATCH);
|
|
171
|
-
const destroyResult = {
|
|
172
|
-
stack,
|
|
173
|
-
success: result.success || isAlreadyDeleted,
|
|
174
|
-
error: result.success || isAlreadyDeleted ? undefined : result.error,
|
|
175
|
-
duration
|
|
176
|
-
};
|
|
177
|
-
if (!destroyResult.success && destroyResult.error) {
|
|
178
|
-
logger.debug("destroyStacksInParallel", `Stack ${stack} failed`, {
|
|
179
|
-
error: destroyResult.error.message
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
onStackCompleteCallback?.(stack, destroyResult.success, duration, destroyResult.success ? undefined : destroyResult.error);
|
|
183
|
-
return destroyResult;
|
|
184
|
-
}));
|
|
185
|
-
return success(mapSettledResults(results, stacks));
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Get stack outputs for a deployed stack
|
|
189
|
-
*/
|
|
190
|
-
async getStackOutputs(appName, stackType) {
|
|
191
|
-
const stackName = getApplicationStackName(appName, stackType);
|
|
192
|
-
const result = await this.cloudFormationService.getStackOutputs(stackName);
|
|
193
|
-
if (!result.success) {
|
|
194
|
-
return failure(new ApplicationError(`Failed to get outputs for ${stackName}`, {
|
|
195
|
-
errorType: "stack_error",
|
|
196
|
-
appName,
|
|
197
|
-
operation: "getStackOutputs",
|
|
198
|
-
stackType,
|
|
199
|
-
details: result.error
|
|
200
|
-
}));
|
|
201
|
-
}
|
|
202
|
-
return success(convertCloudFormationOutputsToRecord(result.data));
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Resolve the website URL for an application from CloudFormation stack outputs.
|
|
206
|
-
* Checks Compute stack for LoadBalancer URL, then CDN stack for CloudFront distribution.
|
|
207
|
-
*/
|
|
208
|
-
async resolveWebsiteUrl(appName) {
|
|
209
|
-
return resolveWebsiteUrl(appName, this.cloudFormationService);
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Destroy all stacks for an application in reverse order.
|
|
213
|
-
* Supports parallel destruction for OpenNext patterns (nextjs/payload).
|
|
214
|
-
*/
|
|
215
|
-
async destroyAllStacks(context, callbacks, resources) {
|
|
216
|
-
return destroyAllStacks(this, context, callbacks, resources);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
1
|
+
import{STACK_NOT_FOUND_PATTERN as v,CDK_NO_STACKS_MATCH as P}from"../../types/constants.js";import{getApplicationStackName as y}from"../../types/operations.js";import{success as S,failure as k}from"@fjall/generator";import{ApplicationError as h}from"../../types/application/ApplicationServiceTypes.js";import{logger as m}from"@fjall/util/logger";import{convertCloudFormationOutputsToRecord as w}from"../supporting/helpers.js";import{destroyAllStacks as R,mapSettledResults as O,resolveWebsiteUrl as A}from"./applicationStackHelpers.js";class M{cdkService;cloudFormationService;aws;constructor(e,r,t){this.cdkService=e,this.cloudFormationService=r,this.aws=t}async runCdkSynth(e){return this.cdkService.runCdkSynth(e)}async deployStack(e,r,t){const n=r.target,a=y(n,e),c=await this.cdkService.runCdkDeploy(r,a,t?.onOutput,t?.onResourceProgress,this.aws);if(c.success){const o=c.data;m.debug("ApplicationStackService","CDK deploy result",{stackName:a,stackType:e,success:!0,message:o.message,status:o.status,skipped:o.details?.skipped,hasOutput:!!o.details?.output});const s=await this.cloudFormationService.getStackOutputs(a);m.debug("ApplicationStackService","Stack outputs after deploy",{stackName:a,hasOutputs:s.success&&s.data.length>0,outputCount:s.success?s.data.length:0});const i=s.success&&s.data.length>0?w(s.data):void 0;return S({stackType:e,stackName:a,skipped:o.details?.skipped===!0,outputs:i})}else{const o=c.error||`Failed to deploy ${e} infrastructure`;return m.error("ApplicationStackService","Stack deployment failed",{stackType:e,target:r.target,error:o}),k(new h(o,{errorType:"deployment_failed",appName:r.target,operation:"deployStack",stackType:e}))}}async deployStacksInParallel(e,r,t){const n=t?.onOutput,a=t?.onResourceProgress,c=t?.onStackComplete,o=await Promise.allSettled(e.map(async s=>{const i=Date.now(),u=await this.deployStack(s,r,{onOutput:n?p=>n(p,s):void 0,onResourceProgress:a?p=>a(p,s):void 0}),d=Date.now()-i,g={stack:s,success:u.success,error:u.success?void 0:u.error,duration:d,...u.success&&u.data.outputs!==void 0?{outputs:u.data.outputs}:{}};return!u.success&&u.error&&m.debug("deployStacksInParallel",`Stack ${s} failed`,{error:u.error.message}),c?.(s,u.success,d,u.success?void 0:u.error),g}));return S(O(o,e))}async destroyStack(e,r,t,n){const a=r.target,c=y(a,e),o=await this.cdkService.runCdkDestroy(r,c,t?.onOutput,t?.onResourceProgress,this.aws,n);return o.success?S({stackType:e,stackName:c,skipped:!1,outputs:void 0}):k(new h(o.error||`Failed to destroy ${e} stack`,{errorType:"destroy_failed",appName:r.target,operation:"destroyStack",stackType:e}))}async destroyStacksInParallel(e,r,t,n){const a=t?.onOutput,c=t?.onResourceProgress,o=t?.onStackComplete,s=await Promise.allSettled(e.map(async i=>{const u=Date.now(),d=await this.destroyStack(i,r,{onOutput:a?f=>a(f,i):void 0,onResourceProgress:c?f=>c(f,i):void 0},n),g=Date.now()-u,p=d.success?"":d.error?.message??"",C=p.includes(v)||p.includes(P),l={stack:i,success:d.success||C,error:d.success||C?void 0:d.error,duration:g};return!l.success&&l.error&&m.debug("destroyStacksInParallel",`Stack ${i} failed`,{error:l.error.message}),o?.(i,l.success,g,l.success?void 0:l.error),l}));return S(O(s,e))}async getStackOutputs(e,r){const t=y(e,r),n=await this.cloudFormationService.getStackOutputs(t);return n.success?S(w(n.data)):k(new h(`Failed to get outputs for ${t}`,{errorType:"stack_error",appName:e,operation:"getStackOutputs",stackType:r,details:n.error}))}async resolveWebsiteUrl(e){return A(e,this.cloudFormationService)}async destroyAllStacks(e,r,t){return R(this,e,r,t)}}export{M as ApplicationStackService};
|
|
@@ -1,248 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { ApplicationError } from "../../types/application/ApplicationServiceTypes.js";
|
|
6
|
-
import { logger } from "@fjall/util/logger";
|
|
7
|
-
/**
|
|
8
|
-
* Convert "doesn't exist" errors to success for destroy operations.
|
|
9
|
-
*/
|
|
10
|
-
export function convertDestroyResultIfNotExists(result, stackType, appName) {
|
|
11
|
-
if (result.success)
|
|
12
|
-
return result;
|
|
13
|
-
const errorMessage = isFailure(result) ? result.error.message : "";
|
|
14
|
-
const isAlreadyDeleted = errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
|
|
15
|
-
errorMessage.includes(CDK_NO_STACKS_MATCH);
|
|
16
|
-
if (isAlreadyDeleted) {
|
|
17
|
-
return success({
|
|
18
|
-
stackName: getApplicationStackName(appName, stackType),
|
|
19
|
-
stackType,
|
|
20
|
-
outputs: {},
|
|
21
|
-
skipped: true
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Handle destroy errors and determine if we should continue or fail
|
|
28
|
-
*/
|
|
29
|
-
export function handleDestroyError(result, stackType) {
|
|
30
|
-
if (result.success)
|
|
31
|
-
return null;
|
|
32
|
-
const errorMessage = isFailure(result) ? result.error.message : "";
|
|
33
|
-
if (errorMessage.includes("TSError") ||
|
|
34
|
-
errorMessage.includes("Unable to compile TypeScript") ||
|
|
35
|
-
errorMessage.includes("Subprocess exited with error")) {
|
|
36
|
-
const tsErrorMatch = errorMessage.match(/infrastructure\.ts\(\d+,\d+\): error TS\d+: .+/);
|
|
37
|
-
const errorDetail = tsErrorMatch
|
|
38
|
-
? tsErrorMatch[0]
|
|
39
|
-
: `Check ${INFRASTRUCTURE_FILENAME} for syntax errors`;
|
|
40
|
-
return {
|
|
41
|
-
continue: false,
|
|
42
|
-
result: failure(new ApplicationError(`CDK synthesis failed: ${errorDetail}`, {
|
|
43
|
-
errorType: "synth_failed",
|
|
44
|
-
operation: `destroy-${stackType}`
|
|
45
|
-
}))
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
if (errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
|
|
49
|
-
errorMessage.includes(CDK_NO_STACKS_MATCH)) {
|
|
50
|
-
return {
|
|
51
|
-
continue: true,
|
|
52
|
-
result: success({ message: "Stack already deleted" })
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
continue: false,
|
|
57
|
-
result: failure(new ApplicationError(`Failed to destroy ${stackType} stack: ${errorMessage}`, {
|
|
58
|
-
errorType: "destroy_failed",
|
|
59
|
-
operation: `destroy-${stackType}`
|
|
60
|
-
}))
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Destroy all stacks for an application in reverse order.
|
|
65
|
-
* Supports parallel destruction for OpenNext patterns (nextjs/payload).
|
|
66
|
-
*/
|
|
67
|
-
export async function destroyAllStacks(service, context, callbacks, resources) {
|
|
68
|
-
const appName = context.target;
|
|
69
|
-
// Use FrameworkRegistry to resolve builder and plan
|
|
70
|
-
const registry = FrameworkRegistry.createDefault();
|
|
71
|
-
const resolved = registry.resolve({ appPath: context.path });
|
|
72
|
-
// Determine destroy strategy from builder plan if available, else fall back to pattern detection
|
|
73
|
-
let useParallelDestroy;
|
|
74
|
-
let destroyOrder;
|
|
75
|
-
if (resolved) {
|
|
76
|
-
const plan = resolved.builder.plan({ appPath: context.path }, resolved.detection);
|
|
77
|
-
useParallelDestroy = plan.parallelDestroy;
|
|
78
|
-
destroyOrder = plan.destroyOrder;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
// No builder matched — infrastructure.ts missing. Use sequential destroy with default order.
|
|
82
|
-
useParallelDestroy = false;
|
|
83
|
-
destroyOrder = getApplicationDestroyOrder({ pattern: null, resources });
|
|
84
|
-
}
|
|
85
|
-
if (useParallelDestroy) {
|
|
86
|
-
const destroyGroups = getParallelDestroyGroups();
|
|
87
|
-
for (const group of destroyGroups) {
|
|
88
|
-
const stacks = group.stacks;
|
|
89
|
-
if (stacks.length === 1) {
|
|
90
|
-
const stackType = stacks[0];
|
|
91
|
-
const stackName = getApplicationStackName(appName, stackType);
|
|
92
|
-
callbacks?.onStackStart?.(stackType, stackName);
|
|
93
|
-
const result = await service.destroyStack(stackType, context, {
|
|
94
|
-
onOutput: callbacks?.onOutput
|
|
95
|
-
? (chunk) => callbacks.onOutput?.(chunk, stackType)
|
|
96
|
-
: undefined,
|
|
97
|
-
onResourceProgress: callbacks?.onResourceProgress
|
|
98
|
-
? (event) => callbacks.onResourceProgress?.(event, stackType)
|
|
99
|
-
: undefined
|
|
100
|
-
});
|
|
101
|
-
const finalResult = convertDestroyResultIfNotExists(result, stackType, appName);
|
|
102
|
-
if (callbacks?.onStackComplete) {
|
|
103
|
-
await callbacks.onStackComplete(stackType, finalResult);
|
|
104
|
-
}
|
|
105
|
-
const errorResult = handleDestroyError(finalResult, stackType);
|
|
106
|
-
if (errorResult) {
|
|
107
|
-
if (errorResult.continue)
|
|
108
|
-
continue;
|
|
109
|
-
return errorResult.result;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
const synthResult = await service.runCdkSynth(context);
|
|
114
|
-
if (!synthResult.success) {
|
|
115
|
-
return failure(new ApplicationError(synthResult.error, {
|
|
116
|
-
errorType: "synth_failed",
|
|
117
|
-
appName,
|
|
118
|
-
operation: "destroy-parallel-synth"
|
|
119
|
-
}));
|
|
120
|
-
}
|
|
121
|
-
callbacks?.onParallelPhaseStart?.(stacks, group.description);
|
|
122
|
-
for (const stackType of stacks) {
|
|
123
|
-
callbacks?.onStackStart?.(stackType, getApplicationStackName(appName, stackType));
|
|
124
|
-
}
|
|
125
|
-
const parallelResult = await service.destroyStacksInParallel(stacks, context, {
|
|
126
|
-
onOutput: callbacks?.onOutput,
|
|
127
|
-
onResourceProgress: callbacks?.onResourceProgress,
|
|
128
|
-
onStackComplete: async (stack, stackSuccess, _duration, error) => {
|
|
129
|
-
const result = stackSuccess
|
|
130
|
-
? success({
|
|
131
|
-
stackName: getApplicationStackName(appName, stack),
|
|
132
|
-
stackType: stack,
|
|
133
|
-
outputs: {}
|
|
134
|
-
})
|
|
135
|
-
: failure(error instanceof ApplicationError
|
|
136
|
-
? error
|
|
137
|
-
: new ApplicationError(error?.message ?? "Stack destruction failed", {
|
|
138
|
-
errorType: "destroy_failed",
|
|
139
|
-
appName,
|
|
140
|
-
operation: `destroy-${stack}`
|
|
141
|
-
}));
|
|
142
|
-
await callbacks?.onStackComplete?.(stack, result);
|
|
143
|
-
}
|
|
144
|
-
}, true);
|
|
145
|
-
if (parallelResult.success) {
|
|
146
|
-
callbacks?.onParallelPhaseComplete?.(parallelResult.data);
|
|
147
|
-
const failures = parallelResult.data.filter((r) => !r.success);
|
|
148
|
-
if (failures.length > 0) {
|
|
149
|
-
const realFailures = failures.filter((f) => f.error &&
|
|
150
|
-
!f.error.message.includes(STACK_NOT_FOUND_PATTERN) &&
|
|
151
|
-
!f.error.message.includes(CDK_NO_STACKS_MATCH));
|
|
152
|
-
if (realFailures.length > 0) {
|
|
153
|
-
const failedStacks = realFailures.map((f) => f.stack).join(", ");
|
|
154
|
-
const errorDetails = realFailures
|
|
155
|
-
.map((f) => `${f.stack}: ${f.error?.message || "Unknown error"}`)
|
|
156
|
-
.join("\n");
|
|
157
|
-
return failure(new ApplicationError(`Failed to destroy stacks: ${failedStacks}\n\n${errorDetails}`, {
|
|
158
|
-
errorType: "destroy_failed",
|
|
159
|
-
appName,
|
|
160
|
-
operation: "destroy-parallel"
|
|
161
|
-
}));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
for (const stackType of destroyOrder) {
|
|
170
|
-
const stackName = getApplicationStackName(appName, stackType);
|
|
171
|
-
callbacks?.onStackStart?.(stackType, stackName);
|
|
172
|
-
const result = await service.destroyStack(stackType, context, {
|
|
173
|
-
onOutput: callbacks?.onOutput
|
|
174
|
-
? (chunk) => callbacks.onOutput?.(chunk, stackType)
|
|
175
|
-
: undefined,
|
|
176
|
-
onResourceProgress: callbacks?.onResourceProgress
|
|
177
|
-
? (event) => callbacks.onResourceProgress?.(event, stackType)
|
|
178
|
-
: undefined
|
|
179
|
-
});
|
|
180
|
-
const finalResult = convertDestroyResultIfNotExists(result, stackType, appName);
|
|
181
|
-
if (callbacks?.onStackComplete) {
|
|
182
|
-
await callbacks.onStackComplete(stackType, finalResult);
|
|
183
|
-
}
|
|
184
|
-
const errorResult = handleDestroyError(finalResult, stackType);
|
|
185
|
-
if (errorResult) {
|
|
186
|
-
if (errorResult.continue)
|
|
187
|
-
continue;
|
|
188
|
-
return errorResult.result;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return success({ message: "All stacks destroyed" });
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Map Promise.allSettled results to ParallelDeploymentResult[], handling
|
|
196
|
-
* rejected promises with a fallback stack identity.
|
|
197
|
-
*/
|
|
198
|
-
export function mapSettledResults(settled, stacks) {
|
|
199
|
-
return settled.map((settledResult, index) => {
|
|
200
|
-
if (settledResult.status === "fulfilled") {
|
|
201
|
-
return settledResult.value;
|
|
202
|
-
}
|
|
203
|
-
const stack = stacks[index] ?? APPLICATION_STACKS.NETWORK;
|
|
204
|
-
return {
|
|
205
|
-
stack,
|
|
206
|
-
success: false,
|
|
207
|
-
error: settledResult.reason instanceof Error
|
|
208
|
-
? settledResult.reason
|
|
209
|
-
: new Error(String(settledResult.reason)),
|
|
210
|
-
duration: 0
|
|
211
|
-
};
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Resolve the website URL for an application from CloudFormation stack outputs.
|
|
216
|
-
* Checks Compute stack for LoadBalancer URL, then CDN stack for CloudFront distribution.
|
|
217
|
-
*/
|
|
218
|
-
export async function resolveWebsiteUrl(appName, cloudFormationService) {
|
|
219
|
-
try {
|
|
220
|
-
const computeStackName = getApplicationStackName(appName, APPLICATION_STACKS.COMPUTE);
|
|
221
|
-
const computeOutputs = await cloudFormationService.getStackOutputs(computeStackName);
|
|
222
|
-
if (computeOutputs.success && computeOutputs.data.length > 0) {
|
|
223
|
-
const lbUrl = computeOutputs.data.find((o) => o.OutputKey?.includes("LoadBalancerUrl"));
|
|
224
|
-
if (lbUrl?.OutputValue) {
|
|
225
|
-
return lbUrl.OutputValue.toLowerCase();
|
|
226
|
-
}
|
|
227
|
-
const lbDns = computeOutputs.data.find((o) => o.OutputKey?.includes("LoadBalancerDnsName"));
|
|
228
|
-
if (lbDns?.OutputValue) {
|
|
229
|
-
return `http://${lbDns.OutputValue.toLowerCase()}`;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const cdnStackName = getApplicationStackName(appName, APPLICATION_STACKS.CDN);
|
|
233
|
-
const cdnOutputs = await cloudFormationService.getStackOutputs(cdnStackName);
|
|
234
|
-
if (cdnOutputs.success && cdnOutputs.data.length > 0) {
|
|
235
|
-
const dist = cdnOutputs.data.find((o) => o.OutputKey?.includes("DistributionDomainName"));
|
|
236
|
-
if (dist?.OutputValue) {
|
|
237
|
-
return `https://${dist.OutputValue.toLowerCase()}`;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return undefined;
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
logger.warn("ApplicationStackService", "URL resolution failed", {
|
|
244
|
-
error: error instanceof Error ? error.message : String(error)
|
|
245
|
-
});
|
|
246
|
-
return undefined;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
1
|
+
import{STACK_NOT_FOUND_PATTERN as T,CDK_NO_STACKS_MATCH as k,INFRASTRUCTURE_FILENAME as E}from"../../types/constants.js";import{APPLICATION_STACKS as D,getApplicationStackName as S,getApplicationDestroyOrder as _,getParallelDestroyGroups as $}from"../../types/operations.js";import{FrameworkRegistry as U}from"../../orchestration/builders/frameworkRegistry.js";import{success as P,failure as h,isFailure as N}from"@fjall/generator";import{ApplicationError as O}from"../../types/application/ApplicationServiceTypes.js";import{logger as K}from"@fjall/util/logger";function R(t,r,e){if(t.success)return t;const n=N(t)?t.error.message:"";return n.includes(T)||n.includes(k)?P({stackName:S(e,r),stackType:r,outputs:{},skipped:!0}):t}function A(t,r){if(t.success)return null;const e=N(t)?t.error.message:"";if(e.includes("TSError")||e.includes("Unable to compile TypeScript")||e.includes("Subprocess exited with error")){const n=e.match(/infrastructure\.ts\(\d+,\d+\): error TS\d+: .+/),s=n?n[0]:`Check ${E} for syntax errors`;return{continue:!1,result:h(new O(`CDK synthesis failed: ${s}`,{errorType:"synth_failed",operation:`destroy-${r}`}))}}return e.includes(T)||e.includes(k)?{continue:!0,result:P({message:"Stack already deleted"})}:{continue:!1,result:h(new O(`Failed to destroy ${r} stack: ${e}`,{errorType:"destroy_failed",operation:`destroy-${r}`}))}}async function v(t,r,e,n){const s=r.target,l=U.createDefault().resolve({appPath:r.path});let f,y;if(l){const u=l.builder.plan({appPath:r.path},l.detection);f=u.parallelDestroy,y=u.destroyOrder}else f=!1,y=_({pattern:null,resources:n});if(f){const u=$();for(const C of u){const m=C.stacks;if(m.length===1){const a=m[0],p=S(s,a);e?.onStackStart?.(a,p);const o=await t.destroyStack(a,r,{onOutput:e?.onOutput?d=>e.onOutput?.(d,a):void 0,onResourceProgress:e?.onResourceProgress?d=>e.onResourceProgress?.(d,a):void 0}),i=R(o,a,s);e?.onStackComplete&&await e.onStackComplete(a,i);const c=A(i,a);if(c){if(c.continue)continue;return c.result}}else{const a=await t.runCdkSynth(r);if(!a.success)return h(new O(a.error,{errorType:"synth_failed",appName:s,operation:"destroy-parallel-synth"}));e?.onParallelPhaseStart?.(m,C.description);for(const o of m)e?.onStackStart?.(o,S(s,o));const p=await t.destroyStacksInParallel(m,r,{onOutput:e?.onOutput,onResourceProgress:e?.onResourceProgress,onStackComplete:async(o,i,c,d)=>{const g=i?P({stackName:S(s,o),stackType:o,outputs:{}}):h(d instanceof O?d:new O(d?.message??"Stack destruction failed",{errorType:"destroy_failed",appName:s,operation:`destroy-${o}`}));await e?.onStackComplete?.(o,g)}},!0);if(p.success){e?.onParallelPhaseComplete?.(p.data);const o=p.data.filter(i=>!i.success);if(o.length>0){const i=o.filter(c=>c.error&&!c.error.message.includes(T)&&!c.error.message.includes(k));if(i.length>0){const c=i.map(g=>g.stack).join(", "),d=i.map(g=>`${g.stack}: ${g.error?.message||"Unknown error"}`).join(`
|
|
2
|
+
`);return h(new O(`Failed to destroy stacks: ${c}
|
|
3
|
+
|
|
4
|
+
${d}`,{errorType:"destroy_failed",appName:s,operation:"destroy-parallel"}))}}}}}}else for(const u of y){const C=S(s,u);e?.onStackStart?.(u,C);const m=await t.destroyStack(u,r,{onOutput:e?.onOutput?o=>e.onOutput?.(o,u):void 0,onResourceProgress:e?.onResourceProgress?o=>e.onResourceProgress?.(o,u):void 0}),a=R(m,u,s);e?.onStackComplete&&await e.onStackComplete(u,a);const p=A(a,u);if(p){if(p.continue)continue;return p.result}}return P({message:"All stacks destroyed"})}function x(t,r){return t.map((e,n)=>e.status==="fulfilled"?e.value:{stack:r[n]??D.NETWORK,success:!1,error:e.reason instanceof Error?e.reason:new Error(String(e.reason)),duration:0})}async function B(t,r){try{const e=S(t,D.COMPUTE),n=await r.getStackOutputs(e);if(n.success&&n.data.length>0){const l=n.data.find(y=>y.OutputKey?.includes("LoadBalancerUrl"));if(l?.OutputValue)return l.OutputValue.toLowerCase();const f=n.data.find(y=>y.OutputKey?.includes("LoadBalancerDnsName"));if(f?.OutputValue)return`http://${f.OutputValue.toLowerCase()}`}const s=S(t,D.CDN),w=await r.getStackOutputs(s);if(w.success&&w.data.length>0){const l=w.data.find(f=>f.OutputKey?.includes("DistributionDomainName"));if(l?.OutputValue)return`https://${l.OutputValue.toLowerCase()}`}return}catch(e){K.warn("ApplicationStackService","URL resolution failed",{error:e instanceof Error?e.message:String(e)});return}}export{R as convertDestroyResultIfNotExists,v as destroyAllStacks,A as handleDestroyError,x as mapSettledResults,B as resolveWebsiteUrl};
|