@fjall/deploy-core 0.89.5 → 0.94.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 (198) hide show
  1. package/LICENSE +50 -21
  2. package/README.md +25 -0
  3. package/dist/.minified +1 -0
  4. package/dist/src/__test-utils__/awsMockHelpers.d.ts +20 -0
  5. package/dist/src/__test-utils__/awsMockHelpers.js +1 -0
  6. package/dist/src/__test-utils__/index.d.ts +1 -0
  7. package/dist/src/__test-utils__/index.js +1 -0
  8. package/dist/src/aws/AwsProvider.js +0 -1
  9. package/dist/src/aws/SimpleAwsProvider.js +1 -70
  10. package/dist/src/aws/index.d.ts +4 -2
  11. package/dist/src/aws/index.js +1 -3
  12. package/dist/src/aws/organisations/accounts.js +10 -10
  13. package/dist/src/aws/organisations/backup.js +4 -2
  14. package/dist/src/aws/organisations/costAllocation.js +4 -2
  15. package/dist/src/aws/organisations/delegatedAdmin.d.ts +9 -0
  16. package/dist/src/aws/organisations/delegatedAdmin.js +43 -0
  17. package/dist/src/aws/organisations/identityCentre.d.ts +1 -1
  18. package/dist/src/aws/organisations/identityCentre.js +6 -2
  19. package/dist/src/aws/organisations/index.d.ts +4 -3
  20. package/dist/src/aws/organisations/index.js +1 -12
  21. package/dist/src/aws/organisations/ipam.js +4 -2
  22. package/dist/src/aws/organisations/organisation.js +27 -18
  23. package/dist/src/aws/organisations/organisationalUnits.d.ts +26 -6
  24. package/dist/src/aws/organisations/organisationalUnits.js +149 -35
  25. package/dist/src/aws/organisations/policies.js +4 -3
  26. package/dist/src/aws/organisations/ram.js +6 -2
  27. package/dist/src/aws/organisations/serviceAccess.js +12 -6
  28. package/dist/src/aws/organisations/trustedAccess.js +6 -2
  29. package/dist/src/aws/organisations/types.d.ts +23 -1
  30. package/dist/src/aws/organisations/types.js +1 -16
  31. package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.d.ts +6 -0
  32. package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.js +1 -0
  33. package/dist/src/aws/utils/cloudformationEventHelpers.d.ts +48 -0
  34. package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -0
  35. package/dist/src/aws/utils/cloudformationEventTypes.d.ts +45 -0
  36. package/dist/src/aws/utils/cloudformationEventTypes.js +1 -0
  37. package/dist/src/aws/utils/cloudformationEvents.d.ts +8 -54
  38. package/dist/src/aws/utils/cloudformationEvents.js +1 -596
  39. package/dist/src/aws/utils/index.d.ts +5 -0
  40. package/dist/src/aws/utils/index.js +1 -0
  41. package/dist/src/aws/utils/stackStatus.js +1 -90
  42. package/dist/src/events/index.d.ts +13 -0
  43. package/dist/src/events/index.js +1 -0
  44. package/dist/src/index.d.ts +34 -17
  45. package/dist/src/index.js +41 -21
  46. package/dist/src/orchestration/__tests__/cascadeTestHelpers.d.ts +12 -0
  47. package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +78 -0
  48. package/dist/src/orchestration/activeDeploymentGuard.d.ts +10 -0
  49. package/dist/src/orchestration/activeDeploymentGuard.js +39 -0
  50. package/dist/src/orchestration/applicationDeploy.js +46 -229
  51. package/dist/src/orchestration/applicationDeployHelpers.d.ts +39 -0
  52. package/dist/src/orchestration/applicationDeployHelpers.js +223 -0
  53. package/dist/src/orchestration/applicationDestroy.d.ts +14 -0
  54. package/dist/src/orchestration/applicationDestroy.js +131 -0
  55. package/dist/src/orchestration/builders/dockerBuilder.d.ts +17 -0
  56. package/dist/src/orchestration/builders/dockerBuilder.js +98 -0
  57. package/dist/src/orchestration/builders/frameworkRegistry.d.ts +23 -0
  58. package/dist/src/orchestration/builders/frameworkRegistry.js +1 -0
  59. package/dist/src/orchestration/builders/index.d.ts +4 -0
  60. package/dist/src/orchestration/builders/index.js +1 -0
  61. package/dist/src/orchestration/builders/openNextBuilder.d.ts +21 -0
  62. package/dist/src/orchestration/builders/openNextBuilder.js +144 -0
  63. package/dist/src/orchestration/cascadeDestroyHelpers.d.ts +30 -0
  64. package/dist/src/orchestration/cascadeDestroyHelpers.js +1 -0
  65. package/dist/src/orchestration/cascadeHelpers.d.ts +46 -0
  66. package/dist/src/orchestration/cascadeHelpers.js +160 -0
  67. package/dist/src/orchestration/contextHelpers.d.ts +46 -2
  68. package/dist/src/orchestration/contextHelpers.js +93 -1
  69. package/dist/src/orchestration/destroy.d.ts +13 -0
  70. package/dist/src/orchestration/destroy.js +67 -0
  71. package/dist/src/orchestration/detectionPipeline.d.ts +2 -11
  72. package/dist/src/orchestration/detectionPipeline.js +29 -10
  73. package/dist/src/orchestration/dockerBuildHelper.d.ts +10 -0
  74. package/dist/src/orchestration/dockerBuildHelper.js +49 -0
  75. package/dist/src/orchestration/dockerInterface.d.ts +4 -2
  76. package/dist/src/orchestration/index.d.ts +8 -1
  77. package/dist/src/orchestration/index.js +1 -3
  78. package/dist/src/orchestration/manifestSecretParser.d.ts +11 -0
  79. package/dist/src/orchestration/manifestSecretParser.js +1 -0
  80. package/dist/src/orchestration/openNextBuild.d.ts +28 -0
  81. package/dist/src/orchestration/openNextBuild.js +243 -0
  82. package/dist/src/orchestration/organisationDeploy.js +110 -233
  83. package/dist/src/orchestration/organisationDestroy.d.ts +24 -0
  84. package/dist/src/orchestration/organisationDestroy.js +189 -0
  85. package/dist/src/orchestration/organisationSetup.d.ts +6 -4
  86. package/dist/src/orchestration/organisationSetup.js +28 -8
  87. package/dist/src/orchestration/resolveOperation.js +68 -6
  88. package/dist/src/orchestration/serviceFactory.d.ts +4 -0
  89. package/dist/src/orchestration/serviceFactory.js +1 -16
  90. package/dist/src/orchestration/spawnHelpers.d.ts +47 -0
  91. package/dist/src/orchestration/spawnHelpers.js +1 -0
  92. package/dist/src/orchestration/stackCleanup.d.ts +39 -0
  93. package/dist/src/orchestration/stackCleanup.js +1 -0
  94. package/dist/src/orchestration/welcomeImageHelper.d.ts +15 -0
  95. package/dist/src/orchestration/welcomeImageHelper.js +64 -0
  96. package/dist/src/services/application/ApplicationStackService.d.ts +21 -30
  97. package/dist/src/services/application/ApplicationStackService.js +16 -234
  98. package/dist/src/services/application/applicationStackHelpers.d.ts +46 -0
  99. package/dist/src/services/application/applicationStackHelpers.js +248 -0
  100. package/dist/src/services/application/index.d.ts +1 -0
  101. package/dist/src/services/application/index.js +1 -1
  102. package/dist/src/services/index.d.ts +6 -0
  103. package/dist/src/services/index.js +1 -0
  104. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -67
  105. package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +10 -2
  106. package/dist/src/services/infrastructure/CdkCommandRunner.js +18 -15
  107. package/dist/src/services/infrastructure/CdkErrorFormatter.js +16 -194
  108. package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -41
  109. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -1
  110. package/dist/src/services/infrastructure/CdkOutputParser.js +2 -33
  111. package/dist/src/services/infrastructure/CdkProcessManager.d.ts +5 -0
  112. package/dist/src/services/infrastructure/CdkProcessManager.js +81 -47
  113. package/dist/src/services/infrastructure/CdkService.d.ts +7 -53
  114. package/dist/src/services/infrastructure/CdkService.js +41 -83
  115. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +50 -0
  116. package/dist/src/services/infrastructure/CdkServiceTypes.js +0 -0
  117. package/dist/src/services/infrastructure/CloudFormationService.js +9 -10
  118. package/dist/src/services/infrastructure/ICdkProcessManager.d.ts +27 -0
  119. package/dist/src/services/infrastructure/ICdkProcessManager.js +1 -0
  120. package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.d.ts +9 -0
  121. package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.js +1 -0
  122. package/dist/src/services/infrastructure/cdkServiceHelpers.d.ts +9 -0
  123. package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -0
  124. package/dist/src/services/infrastructure/constructMapEnrichment.d.ts +7 -0
  125. package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -0
  126. package/dist/src/services/infrastructure/index.d.ts +3 -1
  127. package/dist/src/services/infrastructure/index.js +1 -7
  128. package/dist/src/services/supporting/TemplateHashService.js +1 -1
  129. package/dist/src/services/supporting/helpers.js +1 -81
  130. package/dist/src/services/supporting/index.js +1 -3
  131. package/dist/src/steps/index.d.ts +1 -0
  132. package/dist/src/steps/index.js +1 -0
  133. package/dist/src/steps/stepRegistry.d.ts +71 -0
  134. package/dist/src/steps/stepRegistry.js +505 -0
  135. package/dist/src/types/FjallState.js +1 -118
  136. package/dist/src/types/ProgressEvent.js +1 -48
  137. package/dist/src/types/application/ApplicationServiceTypes.js +1 -30
  138. package/dist/src/types/application/index.js +1 -1
  139. package/dist/src/types/callbacks.d.ts +76 -4
  140. package/dist/src/types/callbacks.js +0 -1
  141. package/dist/src/types/constants.d.ts +2 -0
  142. package/dist/src/types/constants.js +1 -6
  143. package/dist/src/types/credentials.js +0 -1
  144. package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +5 -2
  145. package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
  146. package/dist/src/types/deployment/DeploymentTypes.js +0 -1
  147. package/dist/src/types/deployment/cloudformation.js +0 -1
  148. package/dist/src/types/deployment/index.d.ts +3 -1
  149. package/dist/src/types/deployment/index.js +1 -1
  150. package/dist/src/types/deployment/parallel.js +1 -10
  151. package/dist/src/types/deploymentEventSchema.d.ts +158 -0
  152. package/dist/src/types/deploymentEventSchema.js +1 -0
  153. package/dist/src/types/detection.d.ts +22 -0
  154. package/dist/src/types/detection.js +1 -0
  155. package/dist/src/types/entitlements.d.ts +31 -0
  156. package/dist/src/types/entitlements.js +0 -0
  157. package/dist/src/types/errors/CdkError.js +1 -20
  158. package/dist/src/types/errors/ServiceError.d.ts +2 -1
  159. package/dist/src/types/errors/ServiceError.js +1 -119
  160. package/dist/src/types/errors/index.d.ts +2 -0
  161. package/dist/src/types/errors/index.js +1 -0
  162. package/dist/src/types/events.d.ts +3 -9
  163. package/dist/src/types/events.js +0 -5
  164. package/dist/src/types/frameworkBuilder.d.ts +96 -0
  165. package/dist/src/types/frameworkBuilder.js +8 -0
  166. package/dist/src/types/index.d.ts +19 -4
  167. package/dist/src/types/index.js +1 -9
  168. package/dist/src/types/operations.d.ts +3 -2
  169. package/dist/src/types/operations.js +1 -285
  170. package/dist/src/types/orgConfig.d.ts +2 -10
  171. package/dist/src/types/orgConfig.js +0 -11
  172. package/dist/src/types/params.d.ts +60 -1
  173. package/dist/src/types/patternDetection.d.ts +14 -16
  174. package/dist/src/types/patternDetection.js +14 -18
  175. package/dist/src/types/patternTypes.d.ts +19 -0
  176. package/dist/src/types/patternTypes.js +1 -0
  177. package/dist/src/types/stepDefinitions.d.ts +163 -0
  178. package/dist/src/types/stepDefinitions.js +98 -0
  179. package/dist/src/types/validation.js +0 -1
  180. package/dist/src/util/dockerfileDetection.d.ts +5 -0
  181. package/dist/src/util/dockerfileDetection.js +1 -0
  182. package/dist/src/util/index.d.ts +4 -3
  183. package/dist/src/util/index.js +1 -3
  184. package/dist/src/util/sequencedCallbacks.d.ts +44 -0
  185. package/dist/src/util/sequencedCallbacks.js +1 -0
  186. package/package.json +49 -8
  187. package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +0 -32
  188. package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +0 -228
  189. package/dist/src/aws/utils/errors.d.ts +0 -26
  190. package/dist/src/aws/utils/errors.js +0 -59
  191. package/dist/src/util/fsHelpers.d.ts +0 -4
  192. package/dist/src/util/fsHelpers.js +0 -16
  193. package/dist/src/util/securityHelpers.d.ts +0 -31
  194. package/dist/src/util/securityHelpers.js +0 -124
  195. package/dist/src/util/singleton.d.ts +0 -2
  196. package/dist/src/util/singleton.js +0 -9
  197. package/dist/src/util/sleep.d.ts +0 -4
  198. package/dist/src/util/sleep.js +0 -4
@@ -2,8 +2,8 @@ import { spawn } from "child_process";
2
2
  import { existsSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { createRequire } from "module";
5
- import { filterDangerousEnvVars, maskSensitiveOutput } from "../../util/securityHelpers.js";
6
- import { logger } from "@fjall/util";
5
+ import { logger } from "@fjall/util/logger";
6
+ import { filterDangerousEnvVars, maskSensitiveOutput } from "@fjall/util";
7
7
  import { success, failure } from "@fjall/generator";
8
8
  import { getErrorMessage } from "@fjall/util";
9
9
  /**
@@ -17,8 +17,8 @@ function getCdkBinaryPath() {
17
17
  const cdkEntry = require.resolve("aws-cdk/bin/cdk");
18
18
  return { command: process.execPath, prefixArgs: [cdkEntry] };
19
19
  }
20
- catch {
21
- logger.debug("CdkService", "Failed to resolve aws-cdk binary, falling back to npx");
20
+ catch (err) {
21
+ logger.debug("CdkService", "Failed to resolve aws-cdk binary, falling back to npx", { error: err instanceof Error ? err.message : String(err) });
22
22
  return { command: "npx", prefixArgs: ["cdk"] };
23
23
  }
24
24
  }
@@ -29,28 +29,36 @@ export class CdkProcessManager {
29
29
  runningProcesses = new Map();
30
30
  processCounter = 0;
31
31
  argBuilder;
32
+ exitHandler;
33
+ sigintHandler;
34
+ sigtermHandler;
32
35
  constructor(argBuilder) {
33
36
  this.argBuilder = argBuilder;
34
- // Register cleanup handlers
35
- process.on("exit", () => this.cleanup());
36
- process.on("SIGINT", () => {
37
+ // Register cleanup handlers (stored for deregistration)
38
+ this.exitHandler = () => this.cleanup();
39
+ this.sigintHandler = () => {
37
40
  this.cleanup();
38
41
  process.exit(130);
39
- });
40
- process.on("SIGTERM", () => {
42
+ };
43
+ this.sigtermHandler = () => {
41
44
  this.cleanup();
42
45
  process.exit(143);
43
- });
46
+ };
47
+ process.on("exit", this.exitHandler);
48
+ process.on("SIGINT", this.sigintHandler);
49
+ process.on("SIGTERM", this.sigtermHandler);
44
50
  }
45
51
  forceKillProcess(child) {
46
52
  child.stdout?.destroy();
47
53
  child.stderr?.destroy();
48
54
  child.kill("SIGTERM");
49
- setTimeout(() => {
55
+ const killTimer = setTimeout(() => {
50
56
  if (child.exitCode === null) {
51
57
  child.kill("SIGKILL");
52
58
  }
53
59
  }, SIGKILL_GRACE_PERIOD_MS);
60
+ killTimer.unref();
61
+ child.once("exit", () => clearTimeout(killTimer));
54
62
  }
55
63
  cleanup() {
56
64
  for (const [_name, child] of this.runningProcesses) {
@@ -62,6 +70,12 @@ export class CdkProcessManager {
62
70
  }
63
71
  this.runningProcesses.clear();
64
72
  }
73
+ dispose() {
74
+ this.cleanup();
75
+ process.removeListener("exit", this.exitHandler);
76
+ process.removeListener("SIGINT", this.sigintHandler);
77
+ process.removeListener("SIGTERM", this.sigtermHandler);
78
+ }
65
79
  async runCdkCommandPassthrough(workingDir, args, options) {
66
80
  return new Promise((resolve) => {
67
81
  if (!existsSync(workingDir)) {
@@ -85,25 +99,38 @@ export class CdkProcessManager {
85
99
  shell: false,
86
100
  detached: false
87
101
  });
102
+ if (!child.pid) {
103
+ child.on("error", (err) => {
104
+ logger.debug("CdkProcess", "Spawn error on failed child process", {
105
+ error: err.message
106
+ });
107
+ });
108
+ resolve(failure(`Failed to spawn CDK process - no PID. cwd=${workingDir}, args=${fullArgs.join(" ")}`));
109
+ return;
110
+ }
88
111
  // Track process for cleanup
89
- const processId = `cdk-passthrough-${Date.now()}`;
112
+ const processId = `cdk-passthrough-${++this.processCounter}`;
90
113
  this.runningProcesses.set(processId, child);
114
+ let processKilled = false;
115
+ let settled = false;
91
116
  // Timeout to prevent indefinite hangs (default 30 minutes, matching deploy)
92
- let timeoutHandle;
93
- if (options?.timeout) {
94
- const timeout = options.timeout;
95
- timeoutHandle = setTimeout(() => {
96
- if (!child.killed) {
97
- this.forceKillProcess(child);
98
- this.runningProcesses.delete(processId);
99
- resolve(failure(`CDK command timed out after ${Math.floor(timeout / 60000)} minutes`));
100
- }
101
- }, timeout);
102
- }
117
+ const timeoutMs = options?.timeout ?? 30 * 60 * 1000;
118
+ const timeoutHandle = setTimeout(() => {
119
+ if (!child.killed) {
120
+ processKilled = true;
121
+ this.forceKillProcess(child);
122
+ }
123
+ }, timeoutMs);
103
124
  child.on("close", (code) => {
104
- if (timeoutHandle)
105
- clearTimeout(timeoutHandle);
125
+ clearTimeout(timeoutHandle);
106
126
  this.runningProcesses.delete(processId);
127
+ if (settled)
128
+ return;
129
+ settled = true;
130
+ if (processKilled) {
131
+ resolve(failure("CDK command timed out"));
132
+ return;
133
+ }
107
134
  if (code === 0 || options?.ignoreExitCode) {
108
135
  resolve(success({ exitCode: code || 0 }));
109
136
  }
@@ -112,9 +139,11 @@ export class CdkProcessManager {
112
139
  }
113
140
  });
114
141
  child.on("error", (err) => {
115
- if (timeoutHandle)
116
- clearTimeout(timeoutHandle);
142
+ clearTimeout(timeoutHandle);
117
143
  this.runningProcesses.delete(processId);
144
+ if (settled || processKilled)
145
+ return;
146
+ settled = true;
118
147
  resolve(failure(`Failed to run CDK command: ${err.message}`));
119
148
  });
120
149
  });
@@ -161,8 +190,10 @@ export class CdkProcessManager {
161
190
  // CRITICAL: Attach error handler to prevent uncaught exception
162
191
  // Node.js emits an async 'error' event on the child process even after spawn fails
163
192
  // If we don't handle it, it becomes an uncaught exception and crashes the app
164
- child.on("error", () => {
165
- // Error already handled via the resolve below - this just prevents uncaught exception
193
+ child.on("error", (err) => {
194
+ logger.debug("CdkProcess", "Deferred spawn error on failed child process", {
195
+ error: err.message
196
+ });
166
197
  });
167
198
  resolve(failure(spawnError));
168
199
  return;
@@ -170,15 +201,14 @@ export class CdkProcessManager {
170
201
  // Track process for cleanup
171
202
  const processId = `cdk-${++this.processCounter}`;
172
203
  this.runningProcesses.set(processId, child);
173
- let timeoutHandle;
174
- if (options?.timeout) {
175
- timeoutHandle = setTimeout(() => {
176
- if (!child.killed) {
177
- processKilled = true;
178
- this.forceKillProcess(child);
179
- }
180
- }, options.timeout);
181
- }
204
+ let settled = false;
205
+ const timeoutMs = options?.timeout ?? 30 * 60 * 1000;
206
+ const timeoutHandle = setTimeout(() => {
207
+ if (!child.killed) {
208
+ processKilled = true;
209
+ this.forceKillProcess(child);
210
+ }
211
+ }, timeoutMs);
182
212
  child.stdout?.on("data", (data) => {
183
213
  const chunk = data.toString();
184
214
  output += chunk;
@@ -202,15 +232,19 @@ export class CdkProcessManager {
202
232
  }
203
233
  });
204
234
  child.on("error", (error) => {
205
- if (timeoutHandle)
206
- clearTimeout(timeoutHandle);
235
+ clearTimeout(timeoutHandle);
207
236
  this.runningProcesses.delete(processId);
237
+ if (settled || processKilled)
238
+ return;
239
+ settled = true;
208
240
  resolve(failure(getErrorMessage(error)));
209
241
  });
210
242
  child.on("close", (code) => {
211
- if (timeoutHandle)
212
- clearTimeout(timeoutHandle);
243
+ clearTimeout(timeoutHandle);
213
244
  this.runningProcesses.delete(processId);
245
+ if (settled)
246
+ return;
247
+ settled = true;
214
248
  if (processKilled) {
215
249
  resolve(failure("CDK command timed out"));
216
250
  return;
@@ -219,25 +253,25 @@ export class CdkProcessManager {
219
253
  // For diff command, we need to include stderr in output since CDK outputs errors there
220
254
  const combinedOutput = output + (errorOutput ? `\n${errorOutput}` : "");
221
255
  if (exitedClean) {
256
+ const rawOutput = options?.combineOutput ? combinedOutput : output;
222
257
  resolve(success({
223
- output: args.includes("diff") ? combinedOutput : output,
258
+ output: maskSensitiveOutput(rawOutput),
224
259
  exitCode: code || 0
225
260
  }));
226
261
  return;
227
262
  }
228
- // Parse error output for meaningful messages
263
+ // Parse error output for meaningful messages (match on raw before masking)
229
264
  let errorMessage = errorOutput;
230
265
  if (output) {
231
- // Try to extract error from CDK output
232
266
  const errorMatch = output.match(/❌.*?Error:(.*)$/m);
233
267
  if (errorMatch) {
234
- errorMessage = errorMatch[1].trim();
268
+ errorMessage = errorMatch[1]?.trim() ?? "";
235
269
  }
236
270
  }
237
271
  // Include stdout in the error string so callers can pattern-match on combined output
238
272
  const errorText = errorMessage || `CDK command failed with exit code ${code}`;
239
273
  const fullError = output ? `${errorText}\n${output}` : errorText;
240
- resolve(failure(fullError));
274
+ resolve(failure(maskSensitiveOutput(fullError)));
241
275
  });
242
276
  });
243
277
  }
@@ -1,72 +1,26 @@
1
1
  import { type ResourceEvent } from "../../aws/utils/cloudformationEvents.js";
2
- import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
3
- import type { StepOutput } from "../../types/deployment/DeploymentTypes.js";
2
+ import type { DeploymentContext, StepOutput } from "../../types/deployment/DeploymentTypes.js";
4
3
  import type { AwsProvider } from "../../aws/AwsProvider.js";
5
4
  import type { Result } from "@fjall/generator";
6
5
  import type { CdkError } from "../../types/errors/CdkError.js";
7
- import type { EventLogWriterFactory } from "../../aws/utils/cloudformationEvents.js";
8
6
  import { type CheckDifferencesResult } from "./CdkCommandRunner.js";
9
- export interface CdkContext {
10
- accountId?: string;
11
- region?: string;
12
- environment?: string;
13
- stackName?: string;
14
- managedAccount?: boolean;
15
- accountName?: string;
16
- imageVersion?: string;
17
- orgId?: string;
18
- rootId?: string;
19
- managementAccountId?: string;
20
- ipamPoolId?: string;
21
- fjallOrgId?: string;
22
- fjallOidcConfigured?: string;
23
- orgConfig?: string;
24
- }
25
- export interface CdkOptions {
26
- verbose?: boolean;
27
- noPrompt?: boolean;
28
- outputCallback?: (output: string) => void;
29
- errorCallback?: (error: string) => void;
30
- context?: CdkContext;
31
- timeout?: number;
32
- passThroughCDK?: boolean;
33
- stacks?: string[];
34
- noLookups?: boolean;
35
- useCdkOut?: boolean;
36
- credentials?: {
37
- accessKeyId: string;
38
- secretAccessKey: string;
39
- sessionToken?: string;
40
- };
41
- outputDir?: string;
42
- appDir?: string;
43
- cdkOutputLogger?: {
44
- writeCdkOutput(stream: "stdout" | "stderr", chunk: string): void;
45
- };
46
- }
47
- export interface CdkOutput {
48
- output?: string;
49
- exitCode?: number;
50
- }
51
- export interface CdkServiceOptions {
52
- eventLogWriterFactory?: EventLogWriterFactory;
53
- }
7
+ import type { CdkOptions, CdkOutput, CdkServiceOptions } from "./CdkServiceTypes.js";
8
+ export type { CdkContext, CdkOptions, CdkOutput, CdkServiceOptions } from "./CdkServiceTypes.js";
54
9
  export { type CheckDifferencesResult } from "./CdkCommandRunner.js";
55
10
  export declare class CdkService {
56
11
  private commandRunner;
57
12
  private eventMonitor;
58
13
  constructor(options?: CdkServiceOptions);
14
+ dispose(): void;
59
15
  checkDifferences(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CheckDifferencesResult, CdkError>>;
60
16
  deploy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
61
17
  destroy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
62
18
  runImport(path: string, resourceMappingFile?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
63
19
  synth(path: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
64
20
  bootstrap(accountId: string, region: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
65
- private resolveStackName;
66
- private buildDeploymentCdkContext;
67
21
  runCdkSynth(context: DeploymentContext, onOutput?: (chunk: string) => void): Promise<Result<StepOutput, string>>;
68
- runCdkBootstrap(context: DeploymentContext, onOutput?: (chunk: string) => void): Promise<Result<StepOutput, string>>;
22
+ runCdkBootstrap(context: DeploymentContext, onOutput?: (chunk: string) => void, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
69
23
  runCdkDiff(context: DeploymentContext, onOutput?: (chunk: string) => void): Promise<Result<StepOutput, string>>;
70
- runCdkDeploy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider): Promise<Result<StepOutput, string>>;
71
- runCdkDestroy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, useCdkOut?: boolean): Promise<Result<StepOutput, string>>;
24
+ runCdkDeploy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
25
+ runCdkDestroy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, useCdkOut?: boolean, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
72
26
  }
@@ -1,24 +1,30 @@
1
1
  import { existsSync } from "fs";
2
2
  import { join } from "path";
3
- import { logger } from "@fjall/util";
3
+ import { logger } from "@fjall/util/logger";
4
4
  import { success, failure } from "@fjall/generator";
5
5
  import { DEFAULT_REGION } from "../../aws/utils/regions.js";
6
- import { getApplicationStackName, getOrganisationStackName, isApplicationStack } from "../../types/operations.js";
7
- import { getErrorMessage } from "@fjall/util";
6
+ import { getErrorMessage, maskSensitiveOutput } from "@fjall/util";
8
7
  import { CdkEventMonitor, startStackMonitoring } from "./CdkEventMonitoring.js";
9
8
  import { analyseDeployOutput, analyseDestroyResult, createEnhancedOutputCallback } from "./CdkOutputAnalyser.js";
10
9
  import { CdkCommandRunner } from "./CdkCommandRunner.js";
11
- /** Delay before falling back to the predicted stack name for CloudFormation monitoring */
12
- const STACK_DETECTION_FALLBACK_MS = 5_000;
10
+ import { CdkArgumentBuilder } from "./CdkArgumentBuilder.js";
11
+ import { CdkProcessManager } from "./CdkProcessManager.js";
12
+ import { wrapWithConstructMapEnrichment } from "./constructMapEnrichment.js";
13
+ import { STACK_DETECTION_FALLBACK_MS, resolveStackName, getFallbackStackName, buildDeploymentCdkContext } from "./cdkServiceHelpers.js";
13
14
  export class CdkService {
14
15
  commandRunner;
15
16
  eventMonitor;
16
17
  constructor(options) {
17
- this.commandRunner = new CdkCommandRunner();
18
+ const processManager = options?.processManager ??
19
+ new CdkProcessManager(new CdkArgumentBuilder());
20
+ this.commandRunner = new CdkCommandRunner(processManager);
18
21
  this.eventMonitor = new CdkEventMonitor({
19
22
  eventLogWriterFactory: options?.eventLogWriterFactory
20
23
  });
21
24
  }
25
+ dispose() {
26
+ this.commandRunner.dispose();
27
+ }
22
28
  // Delegate low-level commands to CdkCommandRunner
23
29
  async checkDifferences(path, stackName, options) {
24
30
  return this.commandRunner.checkDifferences(path, stackName, options);
@@ -38,48 +44,12 @@ export class CdkService {
38
44
  async bootstrap(accountId, region, options) {
39
45
  return this.commandRunner.bootstrap(accountId, region, options);
40
46
  }
41
- resolveStackName(stackPattern, context) {
42
- if (stackPattern && !stackPattern.includes("*")) {
43
- return stackPattern;
44
- }
45
- if (stackPattern) {
46
- const stackTypeMatch = stackPattern.match(/\*?(\w+)\*?/);
47
- if (stackTypeMatch) {
48
- const stackType = stackTypeMatch[1];
49
- const appName = context.target || "app";
50
- return isApplicationStack(stackType)
51
- ? getApplicationStackName(appName, stackType)
52
- : `${appName}${stackType}`;
53
- }
54
- return stackPattern;
55
- }
56
- return undefined;
57
- }
58
- buildDeploymentCdkContext(context, accountId, region, options) {
59
- const environment = undefined;
60
- return {
61
- accountId,
62
- region,
63
- environment,
64
- managedAccount: context.isManagedAccount,
65
- ...(options?.includeImageVersion !== false && {
66
- imageVersion: context.imageVersion
67
- }),
68
- orgId: context.orgId,
69
- rootId: context.rootId,
70
- managementAccountId: context.managementAccountId,
71
- ipamPoolId: context.ipamPoolId,
72
- fjallOrgId: context.fjallOrgId,
73
- fjallOidcConfigured: context.fjallOidcConfigured ? "true" : undefined,
74
- orgConfig: context.orgConfig
75
- };
76
- }
77
47
  async runCdkSynth(context, onOutput) {
78
48
  const accountId = context.callerIdentity?.Account;
79
49
  try {
80
50
  const result = await this.synth(context.path, {
81
51
  outputCallback: onOutput,
82
- context: this.buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
52
+ context: buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
83
53
  });
84
54
  if (!result.success) {
85
55
  return failure(result.error || "Failed to synthesise CloudFormation template");
@@ -95,7 +65,7 @@ export class CdkService {
95
65
  return failure(`CDK synth failed: ${getErrorMessage(error)}`);
96
66
  }
97
67
  }
98
- async runCdkBootstrap(context, onOutput) {
68
+ async runCdkBootstrap(context, onOutput, credentials) {
99
69
  const accountId = context.callerIdentity?.Account;
100
70
  const region = context.region || DEFAULT_REGION;
101
71
  try {
@@ -107,7 +77,8 @@ export class CdkService {
107
77
  return failure(`Dependencies not installed. Please run 'npm install' in ${context.path} before deploying.`);
108
78
  }
109
79
  const result = await this.bootstrap(accountId, region, {
110
- outputCallback: onOutput
80
+ outputCallback: onOutput,
81
+ credentials
111
82
  });
112
83
  if (!result.success) {
113
84
  return failure(result.error || "Failed to bootstrap AWS environment");
@@ -124,7 +95,7 @@ export class CdkService {
124
95
  const result = await this.checkDifferences(context.path, undefined, {
125
96
  verbose: context.options?.verbose,
126
97
  outputCallback: onOutput,
127
- context: this.buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
98
+ context: buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
128
99
  });
129
100
  if (!result.success) {
130
101
  return failure(`CDK diff failed: ${result.error.message}`);
@@ -141,7 +112,7 @@ export class CdkService {
141
112
  return failure(`CDK diff failed: ${getErrorMessage(error)}`);
142
113
  }
143
114
  }
144
- async runCdkDeploy(context, stackPattern, onOutput, onResourceProgress, aws) {
115
+ async runCdkDeploy(context, stackPattern, onOutput, onResourceProgress, aws, credentials) {
145
116
  const accountId = context.callerIdentity?.Account;
146
117
  const region = context.region || DEFAULT_REGION;
147
118
  if (!accountId) {
@@ -150,23 +121,16 @@ export class CdkService {
150
121
  if (!aws) {
151
122
  return failure("AwsProvider is required for deployment monitoring.");
152
123
  }
124
+ // Read construct map from fjall-manifest.json (written during synth)
125
+ const enrichedOnResourceProgress = wrapWithConstructMapEnrichment(context.path, onResourceProgress);
153
126
  let cfnMonitor = null;
154
127
  let fallbackMonitoringTimeout;
155
128
  try {
156
- const targetStackName = this.resolveStackName(stackPattern, context) ??
157
- (() => {
158
- const deployType = context.deployType;
159
- if (deployType === "organisation" ||
160
- deployType === "platform" ||
161
- deployType === "account") {
162
- return getOrganisationStackName(deployType);
163
- }
164
- const appName = context.target || "app";
165
- return `${appName}Network`;
166
- })();
129
+ const targetStackName = resolveStackName(stackPattern, context) ??
130
+ getFallbackStackName(context);
167
131
  cfnMonitor = await this.eventMonitor.createEventMonitor("deploy", targetStackName, region, context, aws);
168
132
  if (onOutput) {
169
- onOutput(`Starting CloudFormation deployment of ${targetStackName}...\n`);
133
+ onOutput(maskSensitiveOutput(`Starting CloudFormation deployment of ${targetStackName}...\n`));
170
134
  onOutput(`Monitoring CloudFormation events (CDK process running in background)...\n`);
171
135
  }
172
136
  const state = {
@@ -175,7 +139,7 @@ export class CdkService {
175
139
  stackDetected: false,
176
140
  monitoringPromise: null
177
141
  };
178
- const enhancedOutputCallback = createEnhancedOutputCallback(state, onOutput, cfnMonitor, onResourceProgress);
142
+ const enhancedOutputCallback = createEnhancedOutputCallback(state, onOutput, cfnMonitor, enrichedOnResourceProgress);
179
143
  // Fall back to predicted stack name if actual name not detected in time
180
144
  fallbackMonitoringTimeout = setTimeout(() => {
181
145
  if (!state.stackDetected && cfnMonitor && !state.monitoringPromise) {
@@ -184,7 +148,7 @@ export class CdkService {
184
148
  stackDetected: state.stackDetected,
185
149
  hasOnResourceProgress: !!onResourceProgress
186
150
  });
187
- state.monitoringPromise = startStackMonitoring(cfnMonitor, targetStackName, onResourceProgress);
151
+ state.monitoringPromise = startStackMonitoring(cfnMonitor, targetStackName, enrichedOnResourceProgress);
188
152
  }
189
153
  }, STACK_DETECTION_FALLBACK_MS);
190
154
  const result = await this.deploy(context.path, targetStackName, {
@@ -192,37 +156,31 @@ export class CdkService {
192
156
  outputCallback: enhancedOutputCallback,
193
157
  useCdkOut: true,
194
158
  cdkOutputLogger: cfnMonitor?.getEventLogger() ?? undefined,
195
- context: this.buildDeploymentCdkContext(context, accountId, region)
159
+ context: buildDeploymentCdkContext(context, accountId, region),
160
+ credentials
196
161
  });
197
- clearTimeout(fallbackMonitoringTimeout);
198
- if (cfnMonitor) {
199
- logger.debug("CdkService", "CDK process exited, stopping monitor", {
200
- targetStackName,
201
- stackDetected: state.stackDetected,
202
- hadMonitoringPromise: !!state.monitoringPromise
203
- });
204
- cfnMonitor.stopMonitoring();
205
- }
206
162
  return analyseDeployOutput(state.cdkOutput, result, state.actualStackName);
207
163
  }
208
164
  catch (error) {
209
- clearTimeout(fallbackMonitoringTimeout);
210
- if (cfnMonitor) {
211
- cfnMonitor.stopMonitoring();
212
- }
213
165
  const errorMsg = `CDK deploy failed: ${getErrorMessage(error)}`;
214
166
  logger.error("CdkService", "CDK deployment exception", {
215
167
  error: errorMsg
216
168
  });
217
169
  return failure(errorMsg);
218
170
  }
171
+ finally {
172
+ clearTimeout(fallbackMonitoringTimeout);
173
+ if (cfnMonitor) {
174
+ cfnMonitor.stopMonitoring();
175
+ }
176
+ }
219
177
  }
220
- async runCdkDestroy(context, stackPattern, onOutput, onResourceProgress, aws, useCdkOut) {
178
+ async runCdkDestroy(context, stackPattern, onOutput, onResourceProgress, aws, useCdkOut, credentials) {
221
179
  const accountId = context.callerIdentity?.Account;
222
180
  const region = context.region || DEFAULT_REGION;
223
181
  let cfnMonitor = null;
224
182
  try {
225
- const targetStackName = this.resolveStackName(stackPattern, context);
183
+ const targetStackName = resolveStackName(stackPattern, context);
226
184
  if (accountId && targetStackName && aws) {
227
185
  cfnMonitor = await this.eventMonitor.createEventMonitor("destroy", targetStackName, region, context, aws);
228
186
  cfnMonitor.startMonitoring(targetStackName, (event) => {
@@ -236,20 +194,20 @@ export class CdkService {
236
194
  outputCallback: onOutput,
237
195
  useCdkOut,
238
196
  cdkOutputLogger: cfnMonitor?.getEventLogger() ?? undefined,
239
- context: this.buildDeploymentCdkContext(context, accountId, region, {
197
+ context: buildDeploymentCdkContext(context, accountId, region, {
240
198
  includeImageVersion: false
241
- })
199
+ }),
200
+ credentials
242
201
  });
243
- if (cfnMonitor) {
244
- cfnMonitor.stopMonitoring();
245
- }
246
202
  return analyseDestroyResult(result);
247
203
  }
248
204
  catch (error) {
205
+ return failure(`CDK destroy failed: ${getErrorMessage(error)}`);
206
+ }
207
+ finally {
249
208
  if (cfnMonitor) {
250
209
  cfnMonitor.stopMonitoring();
251
210
  }
252
- return failure(`CDK destroy failed: ${getErrorMessage(error)}`);
253
211
  }
254
212
  }
255
213
  }
@@ -0,0 +1,50 @@
1
+ import type { EventLogWriterFactory } from "../../aws/utils/cloudformationEvents.js";
2
+ import type { ICdkProcessManager } from "./ICdkProcessManager.js";
3
+ export interface CdkContext {
4
+ accountId?: string;
5
+ region?: string;
6
+ environment?: string;
7
+ managedAccount?: boolean;
8
+ accountName?: string;
9
+ imageVersion?: string;
10
+ orgId?: string;
11
+ rootId?: string;
12
+ managementAccountId?: string;
13
+ ipamPoolId?: string;
14
+ fjallOrgId?: string;
15
+ fjallOidcConfigured?: string;
16
+ orgConfig?: string;
17
+ }
18
+ export interface CdkOptions {
19
+ verbose?: boolean;
20
+ noPrompt?: boolean;
21
+ outputCallback?: (output: string) => void;
22
+ errorCallback?: (error: string) => void;
23
+ context?: CdkContext;
24
+ timeout?: number;
25
+ passThroughCDK?: boolean;
26
+ stacks?: string[];
27
+ noLookups?: boolean;
28
+ useCdkOut?: boolean;
29
+ credentials?: {
30
+ accessKeyId: string;
31
+ secretAccessKey: string;
32
+ sessionToken?: string;
33
+ };
34
+ outputDir?: string;
35
+ appDir?: string;
36
+ cdkOutputLogger?: {
37
+ writeCdkOutput(stream: "stdout" | "stderr", chunk: string): void;
38
+ };
39
+ }
40
+ export interface CdkOutput {
41
+ output?: string;
42
+ exitCode?: number;
43
+ }
44
+ export interface CdkServiceOptions {
45
+ eventLogWriterFactory?: EventLogWriterFactory;
46
+ /** Optional process manager for CDK binary resolution. When provided, CdkCommandRunner
47
+ * uses this instead of creating its own CdkProcessManager (which relies on
48
+ * import.meta.url for binary resolution from deploy-core's node_modules). */
49
+ processManager?: ICdkProcessManager;
50
+ }
@@ -2,9 +2,10 @@ import { CloudFormationClient, DeleteStackCommand, DescribeStacksCommand, ListEx
2
2
  import { stackStatusMap } from "../../aws/utils/stackStatus.js";
3
3
  import { success, failure } from "@fjall/generator";
4
4
  import { BaseServiceError } from "../../types/errors/ServiceError.js";
5
- import { getErrorMessage, logger } from "@fjall/util";
6
- import { sleep } from "../../util/sleep.js";
7
- import { STACK_NOT_FOUND_PATTERN } from "../../aws/utils/cloudformationEvents.js";
5
+ import { logger } from "@fjall/util/logger";
6
+ import { getErrorMessage, sleep } from "@fjall/util";
7
+ import { STACK_NOT_FOUND_PATTERN } from "@fjall/util/aws";
8
+ import { extractErrorName } from "../../aws/organisations/types.js";
8
9
  /**
9
10
  * CloudFormation-specific error
10
11
  */
@@ -34,18 +35,16 @@ export class CloudFormationService {
34
35
  * Classify an AWS SDK error into a typed CloudFormationError.
35
36
  */
36
37
  classifyAwsError(error, fallbackMessage, stackName) {
37
- const errorCode = error instanceof Error && "code" in error
38
- ? String(error.code)
39
- : undefined;
38
+ const errorName = extractErrorName(error);
40
39
  const errorMessage = getErrorMessage(error);
41
- if (errorCode === "CredentialsError" || errorCode === "UnauthorizedError") {
40
+ if (errorName === "CredentialsError" || errorName === "UnauthorizedError") {
42
41
  return new CloudFormationError(`AWS credentials error: ${errorMessage}`, "auth_error", stackName, undefined, error, false);
43
42
  }
44
- if (errorCode === "Throttling" ||
45
- errorCode === "TooManyRequestsException") {
43
+ if (errorName === "Throttling" ||
44
+ errorName === "TooManyRequestsException") {
46
45
  return new CloudFormationError(`AWS rate limit exceeded: ${errorMessage}`, "throttled", stackName, undefined, error, true);
47
46
  }
48
- if (errorCode === "NetworkingError" || errorCode === "ENOTFOUND") {
47
+ if (errorName === "NetworkingError" || errorName === "ENOTFOUND") {
49
48
  return new CloudFormationError(`Network error: ${errorMessage}`, "network_error", stackName, undefined, error, true);
50
49
  }
51
50
  return new CloudFormationError(`${fallbackMessage}: ${errorMessage}`, "unknown", stackName, undefined, error);