@fjall/deploy-core 0.89.2

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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/aws/AwsProvider.d.ts +39 -0
  3. package/dist/src/aws/AwsProvider.js +1 -0
  4. package/dist/src/aws/SimpleAwsProvider.d.ts +22 -0
  5. package/dist/src/aws/SimpleAwsProvider.js +73 -0
  6. package/dist/src/aws/index.d.ts +4 -0
  7. package/dist/src/aws/index.js +3 -0
  8. package/dist/src/aws/organisations/accounts.d.ts +21 -0
  9. package/dist/src/aws/organisations/accounts.js +99 -0
  10. package/dist/src/aws/organisations/backup.d.ts +12 -0
  11. package/dist/src/aws/organisations/backup.js +28 -0
  12. package/dist/src/aws/organisations/costAllocation.d.ts +12 -0
  13. package/dist/src/aws/organisations/costAllocation.js +26 -0
  14. package/dist/src/aws/organisations/identityCentre.d.ts +8 -0
  15. package/dist/src/aws/organisations/identityCentre.js +19 -0
  16. package/dist/src/aws/organisations/index.d.ts +16 -0
  17. package/dist/src/aws/organisations/index.js +12 -0
  18. package/dist/src/aws/organisations/ipam.d.ts +7 -0
  19. package/dist/src/aws/organisations/ipam.js +18 -0
  20. package/dist/src/aws/organisations/organisation.d.ts +12 -0
  21. package/dist/src/aws/organisations/organisation.js +94 -0
  22. package/dist/src/aws/organisations/organisationalUnits.d.ts +19 -0
  23. package/dist/src/aws/organisations/organisationalUnits.js +125 -0
  24. package/dist/src/aws/organisations/policies.d.ts +7 -0
  25. package/dist/src/aws/organisations/policies.js +36 -0
  26. package/dist/src/aws/organisations/ram.d.ts +7 -0
  27. package/dist/src/aws/organisations/ram.js +15 -0
  28. package/dist/src/aws/organisations/serviceAccess.d.ts +7 -0
  29. package/dist/src/aws/organisations/serviceAccess.js +38 -0
  30. package/dist/src/aws/organisations/trustedAccess.d.ts +7 -0
  31. package/dist/src/aws/organisations/trustedAccess.js +15 -0
  32. package/dist/src/aws/organisations/types.d.ts +29 -0
  33. package/dist/src/aws/organisations/types.js +16 -0
  34. package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +32 -0
  35. package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +228 -0
  36. package/dist/src/aws/utils/cloudformationEvents.d.ts +98 -0
  37. package/dist/src/aws/utils/cloudformationEvents.js +596 -0
  38. package/dist/src/aws/utils/errors.d.ts +26 -0
  39. package/dist/src/aws/utils/errors.js +59 -0
  40. package/dist/src/aws/utils/regions.d.ts +1 -0
  41. package/dist/src/aws/utils/regions.js +1 -0
  42. package/dist/src/aws/utils/stackStatus.d.ts +23 -0
  43. package/dist/src/aws/utils/stackStatus.js +90 -0
  44. package/dist/src/index.d.ts +35 -0
  45. package/dist/src/index.js +45 -0
  46. package/dist/src/orchestration/applicationDeploy.d.ts +11 -0
  47. package/dist/src/orchestration/applicationDeploy.js +327 -0
  48. package/dist/src/orchestration/contextHelpers.d.ts +9 -0
  49. package/dist/src/orchestration/contextHelpers.js +14 -0
  50. package/dist/src/orchestration/deploy.d.ts +10 -0
  51. package/dist/src/orchestration/deploy.js +42 -0
  52. package/dist/src/orchestration/detectionPipeline.d.ts +23 -0
  53. package/dist/src/orchestration/detectionPipeline.js +65 -0
  54. package/dist/src/orchestration/dockerInterface.d.ts +56 -0
  55. package/dist/src/orchestration/dockerInterface.js +1 -0
  56. package/dist/src/orchestration/domainInterface.d.ts +37 -0
  57. package/dist/src/orchestration/domainInterface.js +1 -0
  58. package/dist/src/orchestration/index.d.ts +8 -0
  59. package/dist/src/orchestration/index.js +3 -0
  60. package/dist/src/orchestration/organisationDeploy.d.ts +16 -0
  61. package/dist/src/orchestration/organisationDeploy.js +382 -0
  62. package/dist/src/orchestration/organisationSetup.d.ts +42 -0
  63. package/dist/src/orchestration/organisationSetup.js +227 -0
  64. package/dist/src/orchestration/resolveOperation.d.ts +10 -0
  65. package/dist/src/orchestration/resolveOperation.js +53 -0
  66. package/dist/src/orchestration/serviceFactory.d.ts +15 -0
  67. package/dist/src/orchestration/serviceFactory.js +16 -0
  68. package/dist/src/services/application/ApplicationStackService.d.ts +93 -0
  69. package/dist/src/services/application/ApplicationStackService.js +436 -0
  70. package/dist/src/services/application/index.d.ts +1 -0
  71. package/dist/src/services/application/index.js +1 -0
  72. package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +12 -0
  73. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +67 -0
  74. package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +30 -0
  75. package/dist/src/services/infrastructure/CdkCommandRunner.js +241 -0
  76. package/dist/src/services/infrastructure/CdkErrorFormatter.d.ts +4 -0
  77. package/dist/src/services/infrastructure/CdkErrorFormatter.js +194 -0
  78. package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +19 -0
  79. package/dist/src/services/infrastructure/CdkEventMonitoring.js +41 -0
  80. package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +43 -0
  81. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +125 -0
  82. package/dist/src/services/infrastructure/CdkOutputParser.d.ts +8 -0
  83. package/dist/src/services/infrastructure/CdkOutputParser.js +33 -0
  84. package/dist/src/services/infrastructure/CdkProcessManager.d.ts +20 -0
  85. package/dist/src/services/infrastructure/CdkProcessManager.js +244 -0
  86. package/dist/src/services/infrastructure/CdkService.d.ts +71 -0
  87. package/dist/src/services/infrastructure/CdkService.js +254 -0
  88. package/dist/src/services/infrastructure/CloudFormationService.d.ts +79 -0
  89. package/dist/src/services/infrastructure/CloudFormationService.js +249 -0
  90. package/dist/src/services/infrastructure/index.d.ts +8 -0
  91. package/dist/src/services/infrastructure/index.js +7 -0
  92. package/dist/src/services/supporting/CdkContextBuilder.d.ts +49 -0
  93. package/dist/src/services/supporting/CdkContextBuilder.js +44 -0
  94. package/dist/src/services/supporting/TemplateHashService.d.ts +67 -0
  95. package/dist/src/services/supporting/TemplateHashService.js +152 -0
  96. package/dist/src/services/supporting/helpers.d.ts +46 -0
  97. package/dist/src/services/supporting/helpers.js +81 -0
  98. package/dist/src/services/supporting/index.d.ts +3 -0
  99. package/dist/src/services/supporting/index.js +3 -0
  100. package/dist/src/types/FjallState.d.ts +50 -0
  101. package/dist/src/types/FjallState.js +118 -0
  102. package/dist/src/types/ProgressEvent.d.ts +35 -0
  103. package/dist/src/types/ProgressEvent.js +48 -0
  104. package/dist/src/types/apiClient.d.ts +34 -0
  105. package/dist/src/types/apiClient.js +1 -0
  106. package/dist/src/types/application/ApplicationServiceTypes.d.ts +56 -0
  107. package/dist/src/types/application/ApplicationServiceTypes.js +30 -0
  108. package/dist/src/types/application/index.d.ts +1 -0
  109. package/dist/src/types/application/index.js +1 -0
  110. package/dist/src/types/callbacks.d.ts +36 -0
  111. package/dist/src/types/callbacks.js +1 -0
  112. package/dist/src/types/constants.d.ts +6 -0
  113. package/dist/src/types/constants.js +6 -0
  114. package/dist/src/types/credentials.d.ts +30 -0
  115. package/dist/src/types/credentials.js +1 -0
  116. package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +23 -0
  117. package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -0
  118. package/dist/src/types/deployment/DeploymentTypes.d.ts +29 -0
  119. package/dist/src/types/deployment/DeploymentTypes.js +1 -0
  120. package/dist/src/types/deployment/cloudformation.d.ts +14 -0
  121. package/dist/src/types/deployment/cloudformation.js +1 -0
  122. package/dist/src/types/deployment/index.d.ts +5 -0
  123. package/dist/src/types/deployment/index.js +1 -0
  124. package/dist/src/types/deployment/parallel.d.ts +46 -0
  125. package/dist/src/types/deployment/parallel.js +10 -0
  126. package/dist/src/types/errors/CdkError.d.ts +14 -0
  127. package/dist/src/types/errors/CdkError.js +20 -0
  128. package/dist/src/types/errors/ServiceError.d.ts +86 -0
  129. package/dist/src/types/errors/ServiceError.js +119 -0
  130. package/dist/src/types/events.d.ts +40 -0
  131. package/dist/src/types/events.js +5 -0
  132. package/dist/src/types/index.d.ts +20 -0
  133. package/dist/src/types/index.js +9 -0
  134. package/dist/src/types/operations.d.ts +193 -0
  135. package/dist/src/types/operations.js +285 -0
  136. package/dist/src/types/orgConfig.d.ts +28 -0
  137. package/dist/src/types/orgConfig.js +11 -0
  138. package/dist/src/types/params.d.ts +74 -0
  139. package/dist/src/types/params.js +1 -0
  140. package/dist/src/types/patternDetection.d.ts +43 -0
  141. package/dist/src/types/patternDetection.js +92 -0
  142. package/dist/src/types/validation.d.ts +12 -0
  143. package/dist/src/types/validation.js +1 -0
  144. package/dist/src/util/fsHelpers.d.ts +4 -0
  145. package/dist/src/util/fsHelpers.js +16 -0
  146. package/dist/src/util/index.d.ts +3 -0
  147. package/dist/src/util/index.js +3 -0
  148. package/dist/src/util/securityHelpers.d.ts +31 -0
  149. package/dist/src/util/securityHelpers.js +124 -0
  150. package/dist/src/util/singleton.d.ts +2 -0
  151. package/dist/src/util/singleton.js +9 -0
  152. package/dist/src/util/sleep.d.ts +4 -0
  153. package/dist/src/util/sleep.js +4 -0
  154. package/package.json +42 -0
@@ -0,0 +1,436 @@
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 { detectPattern, isOpenNextPattern } from "../../types/patternDetection.js";
4
+ import { success, failure, isFailure } from "@fjall/generator";
5
+ import { ApplicationError } from "../../types/application/ApplicationServiceTypes.js";
6
+ import { logger } from "@fjall/util";
7
+ import { convertCloudFormationOutputsToRecord } from "../supporting/helpers.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) {
22
+ this.cdkService = cdkService;
23
+ this.cloudFormationService = cloudFormationService;
24
+ }
25
+ setAwsContext(aws) {
26
+ this.aws = aws;
27
+ }
28
+ /**
29
+ * Deploy a specific stack type for an application
30
+ */
31
+ async deployStack(stackType, context, callbacks) {
32
+ const appName = context.target;
33
+ const stackName = getApplicationStackName(appName, stackType);
34
+ const result = await this.cdkService.runCdkDeploy(context, stackName, callbacks?.onOutput, callbacks?.onResourceProgress, this.aws);
35
+ if (result.success) {
36
+ const stepData = result.data;
37
+ logger.debug("ApplicationStackService", "CDK deploy result", {
38
+ stackName,
39
+ stackType,
40
+ success: true,
41
+ message: stepData.message,
42
+ status: stepData.status,
43
+ skipped: stepData.details?.skipped,
44
+ hasOutput: !!stepData.details?.output
45
+ });
46
+ const outputsResult = await this.cloudFormationService.getStackOutputs(stackName);
47
+ logger.debug("ApplicationStackService", "Stack outputs after deploy", {
48
+ stackName,
49
+ hasOutputs: outputsResult.success && outputsResult.data.length > 0,
50
+ outputCount: outputsResult.success ? outputsResult.data.length : 0
51
+ });
52
+ const outputs = outputsResult.success && outputsResult.data.length > 0
53
+ ? convertCloudFormationOutputsToRecord(outputsResult.data)
54
+ : undefined;
55
+ return success({
56
+ stackType,
57
+ stackName,
58
+ skipped: stepData.details?.skipped === true,
59
+ outputs
60
+ });
61
+ }
62
+ else {
63
+ const errorMsg = result.error || `Failed to deploy ${stackType} infrastructure`;
64
+ logger.error("ApplicationStackService", "Stack deployment failed", {
65
+ stackType,
66
+ target: context.target,
67
+ error: errorMsg
68
+ });
69
+ return failure(new ApplicationError(errorMsg, {
70
+ errorType: "deployment_failed",
71
+ appName: context.target,
72
+ operation: "deployStack",
73
+ stackType
74
+ }));
75
+ }
76
+ }
77
+ /**
78
+ * Deploy multiple stacks in parallel within a deployment phase.
79
+ *
80
+ * Uses Promise.allSettled to ensure all stacks are attempted even if some fail.
81
+ */
82
+ async deployStacksInParallel(stacks, context, callbacks) {
83
+ const onOutputCallback = callbacks?.onOutput;
84
+ const onResourceProgressCallback = callbacks?.onResourceProgress;
85
+ const onStackCompleteCallback = callbacks?.onStackComplete;
86
+ const results = await Promise.allSettled(stacks.map(async (stack) => {
87
+ const startTime = Date.now();
88
+ const result = await this.deployStack(stack, context, {
89
+ onOutput: onOutputCallback
90
+ ? (chunk) => onOutputCallback(chunk, stack)
91
+ : undefined,
92
+ onResourceProgress: onResourceProgressCallback
93
+ ? (event) => onResourceProgressCallback(event, stack)
94
+ : undefined
95
+ });
96
+ const duration = Date.now() - startTime;
97
+ const deploymentResult = {
98
+ stack,
99
+ success: result.success,
100
+ error: result.success ? undefined : result.error,
101
+ duration,
102
+ ...(result.success && result.data.outputs !== undefined
103
+ ? { outputs: result.data.outputs }
104
+ : {})
105
+ };
106
+ if (!result.success && result.error) {
107
+ logger.debug("deployStacksInParallel", `Stack ${stack} failed`, {
108
+ error: result.error.message
109
+ });
110
+ }
111
+ onStackCompleteCallback?.(stack, result.success, duration, result.success ? undefined : result.error);
112
+ return deploymentResult;
113
+ }));
114
+ const deployResults = results.map((settledResult, index) => {
115
+ if (settledResult.status === "fulfilled") {
116
+ return settledResult.value;
117
+ }
118
+ return {
119
+ stack: stacks[index],
120
+ success: false,
121
+ error: settledResult.reason instanceof Error
122
+ ? settledResult.reason
123
+ : new Error(String(settledResult.reason)),
124
+ duration: 0
125
+ };
126
+ });
127
+ return success(deployResults);
128
+ }
129
+ /**
130
+ * Destroy a specific stack type
131
+ */
132
+ async destroyStack(stackType, context, callbacks, useCdkOut) {
133
+ const appName = context.target;
134
+ const stackName = getApplicationStackName(appName, stackType);
135
+ const result = await this.cdkService.runCdkDestroy(context, stackName, callbacks?.onOutput, callbacks?.onResourceProgress, this.aws, useCdkOut);
136
+ if (result.success) {
137
+ return success({
138
+ stackType,
139
+ stackName,
140
+ skipped: false,
141
+ outputs: undefined
142
+ });
143
+ }
144
+ else {
145
+ return failure(new ApplicationError(result.error || `Failed to destroy ${stackType} stack`, {
146
+ errorType: "destroy_failed",
147
+ appName: context.target,
148
+ operation: "destroyStack",
149
+ stackType
150
+ }));
151
+ }
152
+ }
153
+ /**
154
+ * Destroy multiple stacks in parallel within a destroy phase.
155
+ *
156
+ * IMPORTANT: For parallel operations, useCdkOut must be true and CDK synth must
157
+ * be run beforehand. Multiple CDK commands cannot run in parallel because they
158
+ * all try to synthesise to the same cdk.out directory.
159
+ */
160
+ async destroyStacksInParallel(stacks, context, callbacks, useCdkOut) {
161
+ const onOutputCallback = callbacks?.onOutput;
162
+ const onResourceProgressCallback = callbacks?.onResourceProgress;
163
+ const onStackCompleteCallback = callbacks?.onStackComplete;
164
+ const results = await Promise.allSettled(stacks.map(async (stack) => {
165
+ const startTime = Date.now();
166
+ const result = await this.destroyStack(stack, context, {
167
+ onOutput: onOutputCallback
168
+ ? (chunk) => onOutputCallback(chunk, stack)
169
+ : undefined,
170
+ onResourceProgress: onResourceProgressCallback
171
+ ? (event) => onResourceProgressCallback(event, stack)
172
+ : undefined
173
+ }, useCdkOut);
174
+ const duration = Date.now() - startTime;
175
+ const errorMessage = result.success
176
+ ? ""
177
+ : (result.error?.message ?? "");
178
+ const isAlreadyDeleted = errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
179
+ errorMessage.includes(CDK_NO_STACKS_MATCH);
180
+ const destroyResult = {
181
+ stack,
182
+ success: result.success || isAlreadyDeleted,
183
+ error: result.success || isAlreadyDeleted ? undefined : result.error,
184
+ duration
185
+ };
186
+ if (!destroyResult.success && destroyResult.error) {
187
+ logger.debug("destroyStacksInParallel", `Stack ${stack} failed`, {
188
+ error: destroyResult.error.message
189
+ });
190
+ }
191
+ onStackCompleteCallback?.(stack, destroyResult.success, duration, destroyResult.success ? undefined : destroyResult.error);
192
+ return destroyResult;
193
+ }));
194
+ const destroyResults = results.map((settledResult, index) => {
195
+ if (settledResult.status === "fulfilled") {
196
+ return settledResult.value;
197
+ }
198
+ return {
199
+ stack: stacks[index],
200
+ success: false,
201
+ error: settledResult.reason instanceof Error
202
+ ? settledResult.reason
203
+ : new Error(String(settledResult.reason)),
204
+ duration: 0
205
+ };
206
+ });
207
+ return success(destroyResults);
208
+ }
209
+ /**
210
+ * Get stack outputs for a deployed stack
211
+ */
212
+ async getStackOutputs(appName, stackType) {
213
+ const stackName = getApplicationStackName(appName, stackType);
214
+ const result = await this.cloudFormationService.getStackOutputs(stackName);
215
+ if (!result.success) {
216
+ return failure(new ApplicationError(`Failed to get outputs for ${stackName}`, {
217
+ errorType: "stack_error",
218
+ appName,
219
+ operation: "getStackOutputs",
220
+ stackType,
221
+ details: result.error
222
+ }));
223
+ }
224
+ return success(convertCloudFormationOutputsToRecord(result.data));
225
+ }
226
+ /**
227
+ * Resolve the website URL for an application from CloudFormation stack outputs.
228
+ * Checks Compute stack for LoadBalancer URL, then CDN stack for CloudFront distribution.
229
+ */
230
+ async resolveWebsiteUrl(appName) {
231
+ try {
232
+ const computeStackName = getApplicationStackName(appName, APPLICATION_STACKS.COMPUTE);
233
+ const computeOutputs = await this.cloudFormationService.getStackOutputs(computeStackName);
234
+ if (computeOutputs.success && computeOutputs.data.length > 0) {
235
+ const lbUrl = computeOutputs.data.find((o) => o.OutputKey?.includes("LoadBalancerUrl"));
236
+ if (lbUrl?.OutputValue) {
237
+ return lbUrl.OutputValue.toLowerCase();
238
+ }
239
+ const lbDns = computeOutputs.data.find((o) => o.OutputKey?.includes("LoadBalancerDnsName"));
240
+ if (lbDns?.OutputValue) {
241
+ return `http://${lbDns.OutputValue.toLowerCase()}`;
242
+ }
243
+ }
244
+ const cdnStackName = getApplicationStackName(appName, APPLICATION_STACKS.CDN);
245
+ const cdnOutputs = await this.cloudFormationService.getStackOutputs(cdnStackName);
246
+ if (cdnOutputs.success && cdnOutputs.data.length > 0) {
247
+ const dist = cdnOutputs.data.find((o) => o.OutputKey?.includes("DistributionDomainName"));
248
+ if (dist?.OutputValue) {
249
+ return `https://${dist.OutputValue.toLowerCase()}`;
250
+ }
251
+ }
252
+ return undefined;
253
+ }
254
+ catch (error) {
255
+ logger.debug("ApplicationStackService", "URL resolution failed", {
256
+ error: error instanceof Error ? error.message : String(error)
257
+ });
258
+ return undefined;
259
+ }
260
+ }
261
+ /**
262
+ * Destroy all stacks for an application in reverse order.
263
+ * Supports parallel destruction for OpenNext patterns (nextjs/payload).
264
+ */
265
+ async destroyAllStacks(context, callbacks, resources) {
266
+ const patternResult = detectPattern(context.path);
267
+ const pattern = patternResult.pattern;
268
+ const appName = context.target;
269
+ if (isOpenNextPattern(pattern)) {
270
+ const destroyGroups = getParallelDestroyGroups();
271
+ for (const group of destroyGroups) {
272
+ const stacks = group.stacks;
273
+ if (stacks.length === 1) {
274
+ const stackType = stacks[0];
275
+ const stackName = getApplicationStackName(appName, stackType);
276
+ callbacks?.onStackStart?.(stackType, stackName);
277
+ const result = await this.destroyStack(stackType, context, {
278
+ onOutput: callbacks?.onOutput
279
+ ? (chunk) => callbacks.onOutput?.(chunk, stackType)
280
+ : undefined,
281
+ onResourceProgress: callbacks?.onResourceProgress
282
+ ? (event) => callbacks.onResourceProgress?.(event, stackType)
283
+ : undefined
284
+ });
285
+ const finalResult = this.convertDestroyResultIfNotExists(result, stackType, appName);
286
+ if (callbacks?.onStackComplete) {
287
+ await callbacks.onStackComplete(stackType, finalResult);
288
+ }
289
+ const errorResult = this.handleDestroyError(finalResult, stackType);
290
+ if (errorResult) {
291
+ if (errorResult.continue)
292
+ continue;
293
+ return errorResult.result;
294
+ }
295
+ }
296
+ else {
297
+ const synthResult = await this.cdkService.runCdkSynth(context);
298
+ if (!synthResult.success) {
299
+ return failure(new ApplicationError(synthResult.error, {
300
+ errorType: "synth_failed",
301
+ appName,
302
+ operation: "destroy-parallel-synth"
303
+ }));
304
+ }
305
+ callbacks?.onParallelPhaseStart?.(stacks, group.description);
306
+ for (const stackType of stacks) {
307
+ callbacks?.onStackStart?.(stackType, getApplicationStackName(appName, stackType));
308
+ }
309
+ const parallelResult = await this.destroyStacksInParallel(stacks, context, {
310
+ onOutput: callbacks?.onOutput,
311
+ onResourceProgress: callbacks?.onResourceProgress,
312
+ onStackComplete: async (stack, stackSuccess, _duration, error) => {
313
+ const result = stackSuccess
314
+ ? success({
315
+ stackName: `${appName}${stack}`,
316
+ stackType: stack,
317
+ outputs: {}
318
+ })
319
+ : failure(error instanceof ApplicationError
320
+ ? error
321
+ : new ApplicationError(error?.message ?? "Stack destruction failed", {
322
+ errorType: "destroy_failed",
323
+ appName,
324
+ operation: `destroy-${stack}`
325
+ }));
326
+ await callbacks?.onStackComplete?.(stack, result);
327
+ }
328
+ }, true);
329
+ if (parallelResult.success) {
330
+ callbacks?.onParallelPhaseComplete?.(parallelResult.data);
331
+ const failures = parallelResult.data.filter((r) => !r.success);
332
+ if (failures.length > 0) {
333
+ const realFailures = failures.filter((f) => f.error &&
334
+ !f.error.message.includes(STACK_NOT_FOUND_PATTERN) &&
335
+ !f.error.message.includes(CDK_NO_STACKS_MATCH));
336
+ if (realFailures.length > 0) {
337
+ const failedStacks = realFailures
338
+ .map((f) => f.stack)
339
+ .join(", ");
340
+ const errorDetails = realFailures
341
+ .map((f) => `${f.stack}: ${f.error?.message || "Unknown error"}`)
342
+ .join("\n");
343
+ return failure(new ApplicationError(`Failed to destroy stacks: ${failedStacks}\n\n${errorDetails}`, {
344
+ errorType: "destroy_failed",
345
+ appName,
346
+ operation: "destroy-parallel"
347
+ }));
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ else {
355
+ const stackOrder = getApplicationDestroyOrder({ pattern, resources });
356
+ for (const stackType of stackOrder) {
357
+ const stackName = getApplicationStackName(appName, stackType);
358
+ callbacks?.onStackStart?.(stackType, stackName);
359
+ const result = await this.destroyStack(stackType, context, {
360
+ onOutput: callbacks?.onOutput
361
+ ? (chunk) => callbacks.onOutput?.(chunk, stackType)
362
+ : undefined,
363
+ onResourceProgress: callbacks?.onResourceProgress
364
+ ? (event) => callbacks.onResourceProgress?.(event, stackType)
365
+ : undefined
366
+ });
367
+ const finalResult = this.convertDestroyResultIfNotExists(result, stackType, appName);
368
+ if (callbacks?.onStackComplete) {
369
+ await callbacks.onStackComplete(stackType, finalResult);
370
+ }
371
+ const errorResult = this.handleDestroyError(finalResult, stackType);
372
+ if (errorResult) {
373
+ if (errorResult.continue)
374
+ continue;
375
+ return errorResult.result;
376
+ }
377
+ }
378
+ }
379
+ return success({ message: "All stacks destroyed" });
380
+ }
381
+ /**
382
+ * Convert "doesn't exist" errors to success for destroy operations.
383
+ */
384
+ convertDestroyResultIfNotExists(result, stackType, appName) {
385
+ if (result.success)
386
+ return result;
387
+ const errorMessage = isFailure(result) ? result.error.message : "";
388
+ const isAlreadyDeleted = errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
389
+ errorMessage.includes(CDK_NO_STACKS_MATCH);
390
+ if (isAlreadyDeleted) {
391
+ return success({
392
+ stackName: getApplicationStackName(appName, stackType),
393
+ stackType,
394
+ outputs: {}
395
+ });
396
+ }
397
+ return result;
398
+ }
399
+ /**
400
+ * Handle destroy errors and determine if we should continue or fail
401
+ */
402
+ handleDestroyError(result, stackType) {
403
+ if (result.success)
404
+ return null;
405
+ const errorMessage = isFailure(result) ? result.error.message : "";
406
+ if (errorMessage.includes("TSError") ||
407
+ errorMessage.includes("Unable to compile TypeScript") ||
408
+ errorMessage.includes("Subprocess exited with error")) {
409
+ const tsErrorMatch = errorMessage.match(/infrastructure\.ts\(\d+,\d+\): error TS\d+: .+/);
410
+ const errorDetail = tsErrorMatch
411
+ ? tsErrorMatch[0]
412
+ : `Check ${INFRASTRUCTURE_FILENAME} for syntax errors`;
413
+ return {
414
+ continue: false,
415
+ result: failure(new ApplicationError(`CDK synthesis failed: ${errorDetail}`, {
416
+ errorType: "synth_failed",
417
+ operation: `destroy-${stackType}`
418
+ }))
419
+ };
420
+ }
421
+ if (errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
422
+ errorMessage.includes(CDK_NO_STACKS_MATCH)) {
423
+ return {
424
+ continue: true,
425
+ result: success({ message: "Stack already deleted" })
426
+ };
427
+ }
428
+ return {
429
+ continue: false,
430
+ result: failure(new ApplicationError(`Failed to destroy ${stackType} stack: ${errorMessage}`, {
431
+ errorType: "destroy_failed",
432
+ operation: `destroy-${stackType}`
433
+ }))
434
+ };
435
+ }
436
+ }
@@ -0,0 +1 @@
1
+ export { ApplicationStackService } from "./ApplicationStackService.js";
@@ -0,0 +1 @@
1
+ export { ApplicationStackService } from "./ApplicationStackService.js";
@@ -0,0 +1,12 @@
1
+ import type { CdkContext, CdkOptions } from "./CdkService.js";
2
+ export declare class CdkArgumentBuilder {
3
+ buildContextArgs(context?: CdkContext): string[];
4
+ injectCascadeCredentials(env: NodeJS.ProcessEnv, credentials?: CdkOptions["credentials"]): void;
5
+ buildCdkEnv(options?: {
6
+ context?: {
7
+ region?: string;
8
+ accountId?: string;
9
+ };
10
+ credentials?: CdkOptions["credentials"];
11
+ }): NodeJS.ProcessEnv;
12
+ }
@@ -0,0 +1,67 @@
1
+ import { filterDangerousEnvVars } from "../../util/securityHelpers.js";
2
+ export class CdkArgumentBuilder {
3
+ buildContextArgs(context) {
4
+ const args = [];
5
+ if (context?.accountId) {
6
+ args.push("-c", `accountId=${context.accountId}`);
7
+ }
8
+ if (context?.environment) {
9
+ args.push("-c", `environment=${context.environment}`);
10
+ }
11
+ if (context?.managedAccount) {
12
+ args.push("-c", "managedAccount=true");
13
+ }
14
+ if (context?.accountName) {
15
+ args.push("-c", `accountName=${context.accountName}`);
16
+ }
17
+ if (context?.imageVersion) {
18
+ args.push("-c", `imageVersion=${context.imageVersion}`);
19
+ }
20
+ if (context?.orgId) {
21
+ args.push("-c", `orgId=${context.orgId}`);
22
+ }
23
+ if (context?.rootId) {
24
+ args.push("-c", `rootId=${context.rootId}`);
25
+ }
26
+ if (context?.managementAccountId) {
27
+ args.push("-c", `managementAccountId=${context.managementAccountId}`);
28
+ }
29
+ if (context?.ipamPoolId) {
30
+ args.push("-c", `ipamPoolId=${context.ipamPoolId}`);
31
+ }
32
+ if (context?.fjallOrgId) {
33
+ args.push("-c", `fjallOrgId=${context.fjallOrgId}`);
34
+ }
35
+ if (context?.orgConfig) {
36
+ args.push("-c", `orgConfig=${context.orgConfig}`);
37
+ }
38
+ return args;
39
+ }
40
+ injectCascadeCredentials(env, credentials) {
41
+ if (credentials) {
42
+ env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
43
+ env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
44
+ if (credentials.sessionToken) {
45
+ env.AWS_SESSION_TOKEN = credentials.sessionToken;
46
+ }
47
+ }
48
+ }
49
+ buildCdkEnv(options) {
50
+ const env = {
51
+ ...filterDangerousEnvVars(process.env),
52
+ CI: "true",
53
+ FORCE_COLOR: "0",
54
+ CDK_DISABLE_VERSION_CHECK: "1"
55
+ };
56
+ if (options?.context?.region) {
57
+ env.AWS_REGION = options.context.region;
58
+ env.AWS_DEFAULT_REGION = options.context.region;
59
+ env.CDK_DEFAULT_REGION = options.context.region;
60
+ }
61
+ if (options?.context?.accountId) {
62
+ env.CDK_DEFAULT_ACCOUNT = options.context.accountId;
63
+ }
64
+ this.injectCascadeCredentials(env, options?.credentials);
65
+ return env;
66
+ }
67
+ }
@@ -0,0 +1,30 @@
1
+ import type { Result } from "@fjall/generator";
2
+ import { CdkError } from "../../types/errors/CdkError.js";
3
+ import { type DiffDetails } from "./CdkOutputParser.js";
4
+ import type { CdkOptions, CdkOutput } from "./CdkService.js";
5
+ export interface CheckDifferencesResult {
6
+ hasDifferences: boolean;
7
+ output?: string;
8
+ details?: DiffDetails;
9
+ }
10
+ /**
11
+ * Runs low-level CDK CLI commands (synth, deploy, destroy, diff, bootstrap, import).
12
+ * Delegates process management to CdkProcessManager.
13
+ */
14
+ export declare class CdkCommandRunner {
15
+ private processManager;
16
+ constructor();
17
+ checkDifferences(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CheckDifferencesResult, CdkError>>;
18
+ deploy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
19
+ destroy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
20
+ /**
21
+ * Import existing AWS resources into CDK stack
22
+ *
23
+ * NOTE: This method is fully functional but currently unreachable as import
24
+ * functionality is temporarily disabled at the AST layer (astCodeModification.ts).
25
+ * When import is re-enabled, this method will work without changes.
26
+ */
27
+ runImport(path: string, resourceMappingFile?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
28
+ synth(path: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
29
+ bootstrap(accountId: string, region: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
30
+ }