@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,241 @@
1
+ import { tmpdir } from "os";
2
+ import { logger } from "@fjall/util";
3
+ import { success, failure } from "@fjall/generator";
4
+ import { CdkError } from "../../types/errors/CdkError.js";
5
+ import { DEFAULT_REGION } from "../../aws/utils/regions.js";
6
+ import { isCdkError, formatInfrastructureError } from "./CdkErrorFormatter.js";
7
+ import { hasCdkDifferences, parseDiffOutput } from "./CdkOutputParser.js";
8
+ import { CdkArgumentBuilder } from "./CdkArgumentBuilder.js";
9
+ import { CdkProcessManager } from "./CdkProcessManager.js";
10
+ import { DEFAULT_DEPLOY_TIMEOUT_MS } from "./CdkEventMonitoring.js";
11
+ import { analyseBootstrapError } from "./CdkOutputAnalyser.js";
12
+ /** Synth is CPU-bound template generation — 5 minutes is generous */
13
+ const SYNTH_TIMEOUT_MS = 300_000;
14
+ /** Bootstrap creates a single stack with a handful of resources */
15
+ const BOOTSTRAP_TIMEOUT_MS = 180_000;
16
+ /** Import maps existing resources into CDK state — slower than deploy due to resource lookup */
17
+ const IMPORT_TIMEOUT_MS = 1_200_000;
18
+ /** CDK flags used across multiple commands */
19
+ const CDK_FLAGS = Object.freeze({
20
+ CI: "--ci",
21
+ REQUIRE_APPROVAL: "--require-approval",
22
+ APPROVAL_NEVER: "never",
23
+ VERBOSE: "--verbose",
24
+ NO_LOOKUPS: "--no-lookups",
25
+ ALL: "--all"
26
+ });
27
+ /**
28
+ * Runs low-level CDK CLI commands (synth, deploy, destroy, diff, bootstrap, import).
29
+ * Delegates process management to CdkProcessManager.
30
+ */
31
+ export class CdkCommandRunner {
32
+ processManager;
33
+ constructor() {
34
+ const argBuilder = new CdkArgumentBuilder();
35
+ this.processManager = new CdkProcessManager(argBuilder);
36
+ }
37
+ async checkDifferences(path, stackName, options) {
38
+ const args = ["diff"];
39
+ if (stackName) {
40
+ args.push(stackName);
41
+ }
42
+ else {
43
+ args.push(CDK_FLAGS.ALL);
44
+ }
45
+ // Add no-colour flag for cleaner parsing
46
+ args.push("--no-color");
47
+ if (options?.noLookups) {
48
+ args.push(CDK_FLAGS.NO_LOOKUPS);
49
+ }
50
+ const result = await this.processManager.runCdkCommand(path, args, {
51
+ ...options,
52
+ // Diff command returns non-zero if differences exist
53
+ ignoreExitCode: true
54
+ });
55
+ if (!result.success) {
56
+ return failure(new CdkError(formatInfrastructureError(result.error, path), "diff_failed", undefined, undefined, result.error, undefined, false));
57
+ }
58
+ const output = result.data.output || "";
59
+ // Check if the output indicates an error rather than a successful diff
60
+ if (isCdkError(output)) {
61
+ return failure(new CdkError(formatInfrastructureError(output, path), "diff_failed", undefined, undefined, output, undefined, false));
62
+ }
63
+ const hasDiffs = hasCdkDifferences(output);
64
+ const details = parseDiffOutput(output);
65
+ return success({
66
+ hasDifferences: hasDiffs,
67
+ output,
68
+ details
69
+ });
70
+ }
71
+ async deploy(path, stackName, options) {
72
+ const args = ["deploy"];
73
+ if (options?.appDir) {
74
+ args.push("--app", options.appDir);
75
+ }
76
+ else if (options?.useCdkOut) {
77
+ args.push("--app", "cdk.out");
78
+ }
79
+ if (stackName) {
80
+ args.push(stackName);
81
+ }
82
+ else {
83
+ args.push(CDK_FLAGS.ALL);
84
+ }
85
+ // Never require approval in programmatic mode
86
+ args.push(CDK_FLAGS.REQUIRE_APPROVAL, CDK_FLAGS.APPROVAL_NEVER);
87
+ args.push(CDK_FLAGS.CI);
88
+ // Disable version reporting and metadata for cleaner output
89
+ args.push("--no-version-reporting");
90
+ args.push("--no-path-metadata");
91
+ args.push("--no-asset-metadata");
92
+ if (options?.verbose) {
93
+ args.push(CDK_FLAGS.VERBOSE);
94
+ }
95
+ else {
96
+ args.push("--progress", "events");
97
+ }
98
+ const deployOptions = {
99
+ ...options,
100
+ timeout: options?.timeout || DEFAULT_DEPLOY_TIMEOUT_MS
101
+ };
102
+ if (options?.passThroughCDK) {
103
+ return this.processManager.runCdkCommandPassthrough(path, args, deployOptions);
104
+ }
105
+ return this.processManager.runCdkCommand(path, args, deployOptions);
106
+ }
107
+ async destroy(path, stackName, options) {
108
+ const args = ["destroy"];
109
+ if (options?.appDir) {
110
+ args.push("--app", options.appDir);
111
+ }
112
+ else if (options?.useCdkOut) {
113
+ args.push("--app", "cdk.out");
114
+ }
115
+ if (stackName) {
116
+ args.push(stackName);
117
+ }
118
+ else {
119
+ args.push(CDK_FLAGS.ALL);
120
+ }
121
+ args.push("--force");
122
+ if (options?.verbose) {
123
+ args.push(CDK_FLAGS.VERBOSE);
124
+ }
125
+ const destroyOptions = {
126
+ ...options,
127
+ timeout: options?.timeout || DEFAULT_DEPLOY_TIMEOUT_MS
128
+ };
129
+ return this.processManager.runCdkCommand(path, args, destroyOptions);
130
+ }
131
+ /**
132
+ * Import existing AWS resources into CDK stack
133
+ *
134
+ * NOTE: This method is fully functional but currently unreachable as import
135
+ * functionality is temporarily disabled at the AST layer (astCodeModification.ts).
136
+ * When import is re-enabled, this method will work without changes.
137
+ */
138
+ async runImport(path, resourceMappingFile, options) {
139
+ const args = ["import"];
140
+ if (options?.stacks && options.stacks.length > 0) {
141
+ args.push(...options.stacks);
142
+ }
143
+ if (resourceMappingFile) {
144
+ args.push("--resource-mapping", resourceMappingFile);
145
+ }
146
+ args.push(CDK_FLAGS.REQUIRE_APPROVAL, CDK_FLAGS.APPROVAL_NEVER);
147
+ args.push(CDK_FLAGS.CI);
148
+ if (options?.noLookups) {
149
+ args.push(CDK_FLAGS.NO_LOOKUPS);
150
+ }
151
+ if (options?.verbose) {
152
+ args.push(CDK_FLAGS.VERBOSE);
153
+ args.push("--trace");
154
+ args.push("--debug");
155
+ }
156
+ const importOptions = {
157
+ ...options,
158
+ timeout: options?.timeout || IMPORT_TIMEOUT_MS
159
+ };
160
+ if (options?.passThroughCDK) {
161
+ return this.processManager.runCdkCommandPassthrough(path, args, importOptions);
162
+ }
163
+ return this.processManager.runCdkCommand(path, args, importOptions);
164
+ }
165
+ async synth(path, options) {
166
+ options?.outputCallback?.("Synthesising CloudFormation template...\n");
167
+ const lookupArgs = options?.noLookups
168
+ ? [CDK_FLAGS.NO_LOOKUPS]
169
+ : [];
170
+ const outputArgs = options?.outputDir
171
+ ? ["--output", options.outputDir]
172
+ : [];
173
+ const region = options?.context?.region || DEFAULT_REGION;
174
+ const result = await this.processManager.runCdkCommand(path, ["synth", ...lookupArgs, ...outputArgs, CDK_FLAGS.CI, "--quiet"], {
175
+ ...options,
176
+ context: { ...options?.context, region },
177
+ timeout: options?.timeout || SYNTH_TIMEOUT_MS
178
+ });
179
+ if (!result.success) {
180
+ const formatted = result.error
181
+ ? formatInfrastructureError(result.error, path)
182
+ : "Failed to synthesise CloudFormation template";
183
+ return failure(formatted);
184
+ }
185
+ return result;
186
+ }
187
+ async bootstrap(accountId, region, options) {
188
+ const bootstrapPath = tmpdir();
189
+ logger.debug("CdkService", "Starting CDK bootstrap", {
190
+ accountId,
191
+ region,
192
+ bootstrapPath,
193
+ target: `aws://${accountId}/${region}`
194
+ });
195
+ const result = await this.processManager.runCdkCommand(bootstrapPath, [
196
+ "bootstrap",
197
+ `aws://${accountId}/${region}`,
198
+ "--cloudformation-execution-policies",
199
+ "arn:aws:iam::aws:policy/AdministratorAccess",
200
+ "-c",
201
+ `accountId=${accountId}`,
202
+ CDK_FLAGS.REQUIRE_APPROVAL,
203
+ CDK_FLAGS.APPROVAL_NEVER,
204
+ CDK_FLAGS.CI,
205
+ "--quiet",
206
+ "--force"
207
+ ], {
208
+ ...options,
209
+ timeout: options?.timeout || BOOTSTRAP_TIMEOUT_MS,
210
+ context: { region, accountId },
211
+ credentials: options?.credentials,
212
+ skipProjectCheck: true,
213
+ extraEnv: {
214
+ TERM: "dumb",
215
+ CDK_DISABLE_NOTICES: "true",
216
+ CDK_DISABLE_PROGRESS_BAR: "true"
217
+ }
218
+ });
219
+ logger.debug("CdkService", result.success
220
+ ? "Bootstrap completed successfully"
221
+ : "Bootstrap exited with non-zero code", {
222
+ accountId,
223
+ region,
224
+ exitCode: result.success ? result.data.exitCode : undefined,
225
+ output: result.success
226
+ ? result.data.output?.trim() || "(empty)"
227
+ : "(empty)",
228
+ error: result.success ? "(empty)" : result.error.trim() || "(empty)"
229
+ });
230
+ if (!result.success) {
231
+ if (result.error.includes("already bootstrapped")) {
232
+ return success({
233
+ output: "Environment is already bootstrapped",
234
+ exitCode: 0
235
+ });
236
+ }
237
+ return failure(analyseBootstrapError(result.error));
238
+ }
239
+ return success({ output: "AWS environment bootstrapped", exitCode: 0 });
240
+ }
241
+ }
@@ -0,0 +1,4 @@
1
+ export declare function isCdkError(output: string): boolean;
2
+ export declare function getStructuralHint(tsCode: string): string | null;
3
+ export declare function getSourceContext(filePath: string, lineNumber: number, contextLines?: number): string | null;
4
+ export declare function formatInfrastructureError(output: string, appPath?: string): string;
@@ -0,0 +1,194 @@
1
+ import { readFileSync } from "fs";
2
+ import { join } from "path";
3
+ const CDK_ERROR_PATTERNS = [
4
+ // Authentication and credentials errors
5
+ /Unable to locate credentials/i,
6
+ /ExpiredToken/i,
7
+ /InvalidUserPool/i,
8
+ /AccessDenied/i,
9
+ /UnauthorizedAccess/i,
10
+ /AssumeRoleUnauthorizedAccess/i,
11
+ /The security token included in the request is invalid/i,
12
+ /Could not assume role/i,
13
+ // Bootstrap errors
14
+ /This stack uses assets, so the toolkit stack must be deployed/i,
15
+ /Has the environment been bootstrapped/i,
16
+ /Please run 'cdk bootstrap'/i,
17
+ /requires a newer version of the CDK CLI/i,
18
+ // Stack not found errors
19
+ /Stack .* does not exist/i,
20
+ /Unable to resolve AWS account/i,
21
+ // General CDK errors
22
+ /ENOENT.*cdk\.json/i,
23
+ /Cannot find module/i,
24
+ /SyntaxError:/i,
25
+ /TypeError:/i,
26
+ /ReferenceError:/i,
27
+ /Error: /i,
28
+ // Common failure messages
29
+ /❌ .*failed/i,
30
+ /\[ERROR\]/i,
31
+ /fatal:/i
32
+ ];
33
+ const STRUCTURAL_HINTS = {
34
+ TS1005: "Check for missing or extra brackets, braces, or commas",
35
+ TS1135: "Check for mismatched brackets or braces — a common cause is extra `},` or `]` characters",
36
+ TS1136: "Check for mismatched braces in object literals",
37
+ TS1109: "Check for extra commas or mismatched delimiters",
38
+ TS1003: "Check for syntax errors near the indicated position"
39
+ };
40
+ export function isCdkError(output) {
41
+ if (!output)
42
+ return false;
43
+ return CDK_ERROR_PATTERNS.some((pattern) => pattern.test(output));
44
+ }
45
+ export function getStructuralHint(tsCode) {
46
+ return STRUCTURAL_HINTS[tsCode] ?? null;
47
+ }
48
+ export function getSourceContext(filePath, lineNumber, contextLines = 1) {
49
+ try {
50
+ const content = readFileSync(filePath, "utf-8");
51
+ const lines = content.split("\n");
52
+ const start = Math.max(0, lineNumber - 1 - contextLines);
53
+ const end = Math.min(lines.length, lineNumber + contextLines);
54
+ const gutterWidth = String(end).length;
55
+ const formatted = [];
56
+ for (let i = start; i < end; i++) {
57
+ const num = String(i + 1).padStart(gutterWidth);
58
+ const marker = i === lineNumber - 1 ? ">" : " ";
59
+ formatted.push(`${marker} ${num} | ${lines[i]}`);
60
+ }
61
+ return formatted.join("\n");
62
+ }
63
+ catch {
64
+ // File not readable — skip source context display
65
+ return null;
66
+ }
67
+ }
68
+ export function formatInfrastructureError(output, appPath) {
69
+ const lines = output.split("\n");
70
+ // Handle TypeScript compilation errors
71
+ if (output.includes("error TS")) {
72
+ const tsErrorRegex = /([\w./-]+\.ts)\((\d+),(\d+)\): error (TS\d+): (.+)/;
73
+ const parsed = [];
74
+ for (const line of lines) {
75
+ const match = line.match(tsErrorRegex);
76
+ if (match) {
77
+ parsed.push({
78
+ file: match[1],
79
+ line: parseInt(match[2], 10),
80
+ col: parseInt(match[3], 10),
81
+ code: match[4],
82
+ message: match[5]
83
+ });
84
+ }
85
+ }
86
+ if (parsed.length > 0) {
87
+ const seen = new Set();
88
+ const unique = parsed.filter((e) => {
89
+ const key = `${e.file}:${e.line}`;
90
+ if (seen.has(key))
91
+ return false;
92
+ seen.add(key);
93
+ return true;
94
+ });
95
+ const errors = unique.slice(0, 3);
96
+ const firstError = errors[0];
97
+ const parts = [];
98
+ parts.push(`TypeScript compilation failed in ${firstError.file}:\n`);
99
+ if (appPath) {
100
+ const resolvedPath = join(appPath, firstError.file);
101
+ const context = getSourceContext(resolvedPath, firstError.line);
102
+ if (context) {
103
+ parts.push(context);
104
+ parts.push("");
105
+ }
106
+ }
107
+ for (const err of errors) {
108
+ parts.push(`Error on line ${err.line}: ${err.message} (${err.code})`);
109
+ const hint = getStructuralHint(err.code);
110
+ if (hint) {
111
+ parts.push(`Hint: ${hint}`);
112
+ }
113
+ }
114
+ return parts.join("\n");
115
+ }
116
+ }
117
+ // Handle multi-line CDK validation errors
118
+ const multiLineValidationMatch = output.match(/Validation failed with the following errors:([\s\S]*?)(?=\n\s*at\s|\n\n|$)/);
119
+ if (multiLineValidationMatch) {
120
+ const errorLines = multiLineValidationMatch[1]
121
+ .split("\n")
122
+ .map((l) => l.trim())
123
+ .filter((l) => l.length > 0);
124
+ return `Validation failed with the following errors:\n${errorLines.map((l) => ` ${l}`).join("\n")}`;
125
+ }
126
+ // Handle CDK ValidationError (e.g., missing asset directories)
127
+ const validationMatch = output.match(/ValidationError:\s*(.+?)(?:\n|$)/);
128
+ if (validationMatch) {
129
+ const detail = validationMatch[1];
130
+ const constructMatch = output.match(/at path \[([^\]]+)\] in ([\w.-]+)/);
131
+ let formatted = `${detail}`;
132
+ if (constructMatch) {
133
+ formatted += `\nConstruct: ${constructMatch[1]}`;
134
+ }
135
+ if (detail.includes("Cannot find asset")) {
136
+ const assetPath = detail.match(/at (.+)/)?.[1];
137
+ if (assetPath) {
138
+ formatted += `\nHint: The build output directory does not exist. Make sure the build step has completed before deploying`;
139
+ }
140
+ }
141
+ return formatted;
142
+ }
143
+ // Try to extract: file path, code snippet, error type/message
144
+ let fileLine = "";
145
+ let codeSnippet = "";
146
+ let pointer = "";
147
+ let errorMessage = "";
148
+ for (let i = 0; i < lines.length; i++) {
149
+ const line = lines[i];
150
+ const fileMatch = line.match(/^(\/[^:]+\.ts):(\d+)/);
151
+ if (fileMatch && !fileLine) {
152
+ const fullPath = fileMatch[1];
153
+ const lineNum = fileMatch[2];
154
+ const fileName = fullPath.split("/").pop() || fullPath;
155
+ fileLine = `${fileName}:${lineNum}`;
156
+ if (i + 1 < lines.length) {
157
+ codeSnippet = lines[i + 1];
158
+ }
159
+ if (i + 2 < lines.length && lines[i + 2].includes("^")) {
160
+ pointer = lines[i + 2];
161
+ }
162
+ }
163
+ const errorMatch = line.match(/^(\w*Error):\s*(.+)/);
164
+ if (errorMatch && !errorMessage) {
165
+ errorMessage = `${errorMatch[1]}: ${errorMatch[2]}`;
166
+ }
167
+ }
168
+ if (fileLine && errorMessage) {
169
+ let formatted = `Error in ${fileLine}\n`;
170
+ if (codeSnippet) {
171
+ formatted += `${codeSnippet}\n`;
172
+ }
173
+ if (pointer) {
174
+ formatted += `${pointer}\n`;
175
+ }
176
+ formatted += `\n${errorMessage}`;
177
+ return formatted;
178
+ }
179
+ if (errorMessage) {
180
+ return errorMessage;
181
+ }
182
+ // Fallback: return first few meaningful lines, filtering out noise
183
+ const meaningfulLines = lines
184
+ .filter((l) => l.trim() &&
185
+ l.length < 500 &&
186
+ !l.includes("at Module.") &&
187
+ !l.includes("at Object.") &&
188
+ !l.includes("at Function.") &&
189
+ !l.includes("node:internal") &&
190
+ !l.includes("node_modules/ts-node") &&
191
+ !l.includes("node_modules/aws-cdk"))
192
+ .slice(0, 5);
193
+ return meaningfulLines.join("\n") || output;
194
+ }
@@ -0,0 +1,19 @@
1
+ import { type ResourceEvent, type CloudFormationEventMonitor } from "../../aws/utils/cloudformationEvents.js";
2
+ import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
3
+ import type { AwsProvider } from "../../aws/AwsProvider.js";
4
+ import type { EventLogWriterFactory } from "../../aws/utils/cloudformationEvents.js";
5
+ /** Deploy/destroy default — CloudFormation stacks can take up to 30 minutes */
6
+ export declare const DEFAULT_DEPLOY_TIMEOUT_MS = 1800000;
7
+ export declare function startStackMonitoring(monitor: CloudFormationEventMonitor, stackName: string, onResourceProgress?: (event: ResourceEvent) => void): Promise<{
8
+ success: boolean;
9
+ status?: string;
10
+ failureReason?: string;
11
+ logPath?: string;
12
+ }>;
13
+ export declare class CdkEventMonitor {
14
+ private eventLogWriterFactory?;
15
+ constructor(options?: {
16
+ eventLogWriterFactory?: EventLogWriterFactory;
17
+ });
18
+ createEventMonitor(operation: "deploy" | "destroy", stackName: string, region: string, context: DeploymentContext, aws: AwsProvider): Promise<CloudFormationEventMonitor>;
19
+ }
@@ -0,0 +1,41 @@
1
+ import { logger, getErrorMessage } from "@fjall/util";
2
+ import { CloudFormationFailureAnalyser } from "../../aws/utils/CloudFormationFailureAnalyser.js";
3
+ /** Deploy/destroy default — CloudFormation stacks can take up to 30 minutes */
4
+ export const DEFAULT_DEPLOY_TIMEOUT_MS = 1_800_000;
5
+ export function startStackMonitoring(monitor, stackName, onResourceProgress) {
6
+ return monitor
7
+ .waitForStackComplete(stackName, {
8
+ timeout: DEFAULT_DEPLOY_TIMEOUT_MS,
9
+ pollInterval: 2000,
10
+ onResourceUpdate: onResourceProgress,
11
+ onStackComplete: (_success, _message) => {
12
+ // Stack deployment completed — no console output per architecture
13
+ }
14
+ })
15
+ .catch((err) => {
16
+ logger.debug("CdkService", "Stack monitoring failed", {
17
+ error: getErrorMessage(err)
18
+ });
19
+ return {
20
+ success: false,
21
+ failureReason: "Monitoring failed"
22
+ };
23
+ });
24
+ }
25
+ export class CdkEventMonitor {
26
+ eventLogWriterFactory;
27
+ constructor(options) {
28
+ this.eventLogWriterFactory = options?.eventLogWriterFactory;
29
+ }
30
+ async createEventMonitor(operation, stackName, region, context, aws) {
31
+ const cloudFormationEventsModule = await import("../../aws/utils/cloudformationEvents.js");
32
+ const monitor = new cloudFormationEventsModule.CloudFormationEventMonitor(aws, {
33
+ failureAnalyser: new CloudFormationFailureAnalyser(),
34
+ eventLogWriterFactory: this.eventLogWriterFactory
35
+ });
36
+ const deploymentId = `${operation}-${Date.now()}`;
37
+ const deploymentName = context.target || context.deployType || "deployment";
38
+ monitor.enableLogging(deploymentId, stackName, region, deploymentName);
39
+ return monitor;
40
+ }
41
+ }
@@ -0,0 +1,43 @@
1
+ import { type ResourceEvent, type CloudFormationEventMonitor } from "../../aws/utils/cloudformationEvents.js";
2
+ import type { StepOutput } from "../../types/deployment/DeploymentTypes.js";
3
+ import type { Result } from "@fjall/generator";
4
+ import type { CdkOutput } from "./CdkService.js";
5
+ /**
6
+ * Analyse CDK deploy output and classify the result as success/skip/failure.
7
+ */
8
+ export declare function analyseDeployOutput(cdkOutput: string, result: Result<CdkOutput, string>, stackName: string): Result<StepOutput, string>;
9
+ /**
10
+ * Classify a bootstrap error into a user-friendly message.
11
+ */
12
+ export declare function analyseBootstrapError(errorInfo: string): string;
13
+ /**
14
+ * Analyse CDK destroy result and classify it.
15
+ */
16
+ export declare function analyseDestroyResult(result: Result<CdkOutput, string>): Result<StepOutput, string>;
17
+ /** Result of attempting to detect a stack name from a CDK output chunk */
18
+ export interface StackDetectionResult {
19
+ detected: boolean;
20
+ stackName?: string;
21
+ }
22
+ /**
23
+ * Try to detect the actual CloudFormation stack name from a CDK output chunk.
24
+ * CDK outputs the stack name in several formats during deployment.
25
+ */
26
+ export declare function detectStackNameFromChunk(chunk: string): StackDetectionResult;
27
+ /** Mutable state container for deploy monitoring, to avoid closure complexity in CdkService */
28
+ export interface DeployMonitoringState {
29
+ cdkOutput: string;
30
+ actualStackName: string;
31
+ stackDetected: boolean;
32
+ monitoringPromise: Promise<{
33
+ success: boolean;
34
+ status?: string;
35
+ failureReason?: string;
36
+ logPath?: string;
37
+ }> | null;
38
+ }
39
+ /**
40
+ * Create an enhanced output callback that detects the actual stack name from CDK output
41
+ * and starts CloudFormation monitoring once detected.
42
+ */
43
+ export declare function createEnhancedOutputCallback(state: DeployMonitoringState, originalOutputCallback: ((chunk: string) => void) | undefined, cfnMonitor: CloudFormationEventMonitor | null, onResourceProgress: ((event: ResourceEvent) => void) | undefined): (chunk: string) => void;
@@ -0,0 +1,125 @@
1
+ import { logger } from "@fjall/util";
2
+ import { STACK_NOT_FOUND_PATTERN, CDK_NO_STACKS_MATCH } from "../../aws/utils/cloudformationEvents.js";
3
+ import { success, failure } from "@fjall/generator";
4
+ import { formatInfrastructureError } from "./CdkErrorFormatter.js";
5
+ import { startStackMonitoring } from "./CdkEventMonitoring.js";
6
+ /**
7
+ * Analyse CDK deploy output and classify the result as success/skip/failure.
8
+ */
9
+ export function analyseDeployOutput(cdkOutput, result, stackName) {
10
+ const stackSpecificNoResourcesPattern = new RegExp(`${stackName}[:\\s|]*.*?(?:stack has no resources|skipping deployment)`, "i");
11
+ const hasEmptyStack = stackSpecificNoResourcesPattern.test(cdkOutput);
12
+ const hasNoChanges = cdkOutput.includes("no changes") ||
13
+ cdkOutput.includes("Stack is up to date") ||
14
+ cdkOutput.includes("No changes to deploy");
15
+ const noStacksMatch = cdkOutput.includes(CDK_NO_STACKS_MATCH) ||
16
+ (!result.success && result.error.includes(CDK_NO_STACKS_MATCH));
17
+ if (result.success) {
18
+ return success({
19
+ message: hasEmptyStack
20
+ ? "Stack has no resources, skipped"
21
+ : hasNoChanges
22
+ ? "Infrastructure already up to date"
23
+ : "Deployment successful",
24
+ status: hasNoChanges ? "NO_CHANGES" : undefined,
25
+ details: { output: cdkOutput, skipped: hasEmptyStack }
26
+ });
27
+ }
28
+ if (noStacksMatch) {
29
+ return success({
30
+ message: "Stack not defined, skipped",
31
+ status: "SKIPPED",
32
+ details: { output: cdkOutput, skipped: true }
33
+ });
34
+ }
35
+ let errorMessage = result.error || "Deployment failed";
36
+ if (cdkOutput.includes("failed:")) {
37
+ const errorMatch = cdkOutput.match(/failed: .*?: (.*?)(?:\n|$)/);
38
+ if (errorMatch) {
39
+ errorMessage = errorMatch[1];
40
+ }
41
+ }
42
+ logger.error("CdkService", "CDK deployment returned failure", {
43
+ errorMessage
44
+ });
45
+ return failure(errorMessage);
46
+ }
47
+ /**
48
+ * Classify a bootstrap error into a user-friendly message.
49
+ */
50
+ export function analyseBootstrapError(errorInfo) {
51
+ if (errorInfo.includes("Access Denied") ||
52
+ errorInfo.includes("AccessDenied")) {
53
+ return "Access denied. Check your AWS credentials and permissions";
54
+ }
55
+ if (errorInfo.includes("ExpiredToken")) {
56
+ return "AWS credentials have expired. Please refresh your credentials";
57
+ }
58
+ if (errorInfo.includes("ENOTFOUND") || errorInfo.includes("ECONNREFUSED")) {
59
+ return "Network error. Please check your internet connection and try again";
60
+ }
61
+ if (errorInfo.includes("timeout")) {
62
+ return "Bootstrap operation timed out";
63
+ }
64
+ return formatInfrastructureError(errorInfo);
65
+ }
66
+ /**
67
+ * Analyse CDK destroy result and classify it.
68
+ */
69
+ export function analyseDestroyResult(result) {
70
+ if (!result.success) {
71
+ const stackDoesNotExist = result.error.includes(STACK_NOT_FOUND_PATTERN) ||
72
+ result.error.includes(CDK_NO_STACKS_MATCH);
73
+ if (stackDoesNotExist) {
74
+ return success({
75
+ message: "Stack already deleted or does not exist",
76
+ details: { skipped: true }
77
+ });
78
+ }
79
+ return failure(result.error || "Destroy failed");
80
+ }
81
+ return success({ message: "Stack destroyed successfully" });
82
+ }
83
+ /**
84
+ * Try to detect the actual CloudFormation stack name from a CDK output chunk.
85
+ * CDK outputs the stack name in several formats during deployment.
86
+ */
87
+ export function detectStackNameFromChunk(chunk) {
88
+ // Pattern 1: "Deploying stack <name>"
89
+ const deployMatch = chunk.match(/Deploying stack\s+([^\s]+)/);
90
+ if (deployMatch) {
91
+ return { detected: true, stackName: deployMatch[1] };
92
+ }
93
+ // Pattern 2: "<stack-name> | <number>/<number> |"
94
+ const progressMatch = chunk.match(/^([^\s|]+)\s*\|\s*\d+\/\d+/m);
95
+ if (progressMatch) {
96
+ return { detected: true, stackName: progressMatch[1] };
97
+ }
98
+ // Pattern 3: "Stack ARN:" line contains the stack name
99
+ const arnMatch = chunk.match(/arn:aws:cloudformation:[^:]+:[^:]+:stack\/([^/]+)\//);
100
+ if (arnMatch) {
101
+ return { detected: true, stackName: arnMatch[1] };
102
+ }
103
+ return { detected: false };
104
+ }
105
+ /**
106
+ * Create an enhanced output callback that detects the actual stack name from CDK output
107
+ * and starts CloudFormation monitoring once detected.
108
+ */
109
+ export function createEnhancedOutputCallback(state, originalOutputCallback, cfnMonitor, onResourceProgress) {
110
+ return (chunk) => {
111
+ state.cdkOutput += chunk;
112
+ originalOutputCallback?.(chunk);
113
+ if (!state.stackDetected) {
114
+ const detection = detectStackNameFromChunk(chunk);
115
+ if (detection.detected && detection.stackName) {
116
+ state.actualStackName = detection.stackName;
117
+ state.stackDetected = true;
118
+ }
119
+ // Start monitoring once we detect the actual stack name
120
+ if (state.stackDetected && cfnMonitor && !state.monitoringPromise) {
121
+ state.monitoringPromise = startStackMonitoring(cfnMonitor, state.actualStackName, onResourceProgress);
122
+ }
123
+ }
124
+ };
125
+ }
@@ -0,0 +1,8 @@
1
+ export declare function hasCdkDifferences(output: string | undefined): boolean;
2
+ export interface DiffDetails {
3
+ hasSecurityChanges: boolean;
4
+ resourceChanges: number;
5
+ outputChanges: number;
6
+ resources: string[];
7
+ }
8
+ export declare function parseDiffOutput(output: string): DiffDetails;