@fjall/deploy-core 0.94.0 → 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.
Files changed (54) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/organisations/accounts.js +1 -99
  3. package/dist/src/aws/organisations/backup.js +1 -30
  4. package/dist/src/aws/organisations/costAllocation.js +1 -28
  5. package/dist/src/aws/organisations/delegatedAdmin.js +3 -43
  6. package/dist/src/aws/organisations/identityCentre.js +1 -23
  7. package/dist/src/aws/organisations/ipam.js +1 -20
  8. package/dist/src/aws/organisations/organisation.js +1 -103
  9. package/dist/src/aws/organisations/organisationalUnits.js +1 -239
  10. package/dist/src/aws/organisations/policies.js +1 -37
  11. package/dist/src/aws/organisations/ram.js +1 -19
  12. package/dist/src/aws/organisations/serviceAccess.js +1 -44
  13. package/dist/src/aws/organisations/trustedAccess.js +1 -19
  14. package/dist/src/aws/utils/regions.js +1 -1
  15. package/dist/src/index.js +1 -65
  16. package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +1 -78
  17. package/dist/src/orchestration/activeDeploymentGuard.js +5 -39
  18. package/dist/src/orchestration/applicationDeploy.js +1 -149
  19. package/dist/src/orchestration/applicationDeployHelpers.js +4 -223
  20. package/dist/src/orchestration/applicationDestroy.js +1 -131
  21. package/dist/src/orchestration/builders/dockerBuilder.js +1 -98
  22. package/dist/src/orchestration/builders/openNextBuilder.js +1 -144
  23. package/dist/src/orchestration/cascadeHelpers.js +1 -160
  24. package/dist/src/orchestration/contextHelpers.js +1 -107
  25. package/dist/src/orchestration/deploy.js +1 -42
  26. package/dist/src/orchestration/destroy.js +1 -67
  27. package/dist/src/orchestration/detectionPipeline.js +1 -84
  28. package/dist/src/orchestration/dockerBuildHelper.js +1 -49
  29. package/dist/src/orchestration/dockerInterface.js +0 -1
  30. package/dist/src/orchestration/domainInterface.js +0 -1
  31. package/dist/src/orchestration/openNextBuild.js +3 -243
  32. package/dist/src/orchestration/organisationDeploy.js +3 -284
  33. package/dist/src/orchestration/organisationDestroy.js +3 -189
  34. package/dist/src/orchestration/organisationSetup.js +1 -247
  35. package/dist/src/orchestration/resolveOperation.js +1 -123
  36. package/dist/src/orchestration/welcomeImageHelper.js +1 -64
  37. package/dist/src/services/application/ApplicationStackService.js +1 -218
  38. package/dist/src/services/application/applicationStackHelpers.js +4 -248
  39. package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -244
  40. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -125
  41. package/dist/src/services/infrastructure/CdkProcessManager.js +3 -278
  42. package/dist/src/services/infrastructure/CdkService.js +3 -213
  43. package/dist/src/services/infrastructure/CloudFormationService.js +1 -248
  44. package/dist/src/services/infrastructure/ICdkProcessManager.js +0 -1
  45. package/dist/src/services/supporting/CdkContextBuilder.js +1 -44
  46. package/dist/src/services/supporting/TemplateHashService.js +1 -152
  47. package/dist/src/steps/stepRegistry.js +1 -505
  48. package/dist/src/types/apiClient.js +0 -1
  49. package/dist/src/types/detection.js +0 -1
  50. package/dist/src/types/frameworkBuilder.js +0 -8
  51. package/dist/src/types/params.js +0 -1
  52. package/dist/src/types/patternDetection.js +1 -88
  53. package/dist/src/types/stepDefinitions.js +1 -98
  54. package/package.json +4 -4
@@ -1,218 +1 @@
1
- import { STACK_NOT_FOUND_PATTERN, CDK_NO_STACKS_MATCH } from "../../types/constants.js";
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 { STACK_NOT_FOUND_PATTERN, CDK_NO_STACKS_MATCH, INFRASTRUCTURE_FILENAME } from "../../types/constants.js";
2
- import { APPLICATION_STACKS, getApplicationStackName, getApplicationDestroyOrder, getParallelDestroyGroups } from "../../types/operations.js";
3
- import { FrameworkRegistry } from "../../orchestration/builders/frameworkRegistry.js";
4
- import { success, failure, isFailure } from "@fjall/generator";
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};