@fjall/deploy-core 0.89.4 → 0.89.6

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 (202) 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.d.ts +2 -2
  9. package/dist/src/aws/AwsProvider.js +0 -1
  10. package/dist/src/aws/SimpleAwsProvider.d.ts +2 -3
  11. package/dist/src/aws/SimpleAwsProvider.js +1 -73
  12. package/dist/src/aws/index.d.ts +4 -2
  13. package/dist/src/aws/index.js +1 -3
  14. package/dist/src/aws/organisations/accounts.js +10 -10
  15. package/dist/src/aws/organisations/backup.js +4 -2
  16. package/dist/src/aws/organisations/costAllocation.js +4 -2
  17. package/dist/src/aws/organisations/delegatedAdmin.d.ts +9 -0
  18. package/dist/src/aws/organisations/delegatedAdmin.js +43 -0
  19. package/dist/src/aws/organisations/identityCentre.d.ts +1 -1
  20. package/dist/src/aws/organisations/identityCentre.js +6 -2
  21. package/dist/src/aws/organisations/index.d.ts +4 -3
  22. package/dist/src/aws/organisations/index.js +1 -12
  23. package/dist/src/aws/organisations/ipam.js +4 -2
  24. package/dist/src/aws/organisations/organisation.js +27 -18
  25. package/dist/src/aws/organisations/organisationalUnits.d.ts +26 -6
  26. package/dist/src/aws/organisations/organisationalUnits.js +149 -35
  27. package/dist/src/aws/organisations/policies.js +4 -3
  28. package/dist/src/aws/organisations/ram.js +6 -2
  29. package/dist/src/aws/organisations/serviceAccess.js +12 -6
  30. package/dist/src/aws/organisations/trustedAccess.js +6 -2
  31. package/dist/src/aws/organisations/types.d.ts +23 -1
  32. package/dist/src/aws/organisations/types.js +1 -16
  33. package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.d.ts +6 -0
  34. package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.js +1 -0
  35. package/dist/src/aws/utils/cloudformationEventHelpers.d.ts +48 -0
  36. package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -0
  37. package/dist/src/aws/utils/cloudformationEventTypes.d.ts +45 -0
  38. package/dist/src/aws/utils/cloudformationEventTypes.js +1 -0
  39. package/dist/src/aws/utils/cloudformationEvents.d.ts +8 -54
  40. package/dist/src/aws/utils/cloudformationEvents.js +1 -596
  41. package/dist/src/aws/utils/index.d.ts +5 -0
  42. package/dist/src/aws/utils/index.js +1 -0
  43. package/dist/src/aws/utils/stackStatus.js +1 -90
  44. package/dist/src/events/index.d.ts +13 -0
  45. package/dist/src/events/index.js +1 -0
  46. package/dist/src/index.d.ts +34 -17
  47. package/dist/src/index.js +41 -21
  48. package/dist/src/orchestration/__tests__/cascadeTestHelpers.d.ts +12 -0
  49. package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +78 -0
  50. package/dist/src/orchestration/activeDeploymentGuard.d.ts +10 -0
  51. package/dist/src/orchestration/activeDeploymentGuard.js +39 -0
  52. package/dist/src/orchestration/applicationDeploy.js +46 -224
  53. package/dist/src/orchestration/applicationDeployHelpers.d.ts +39 -0
  54. package/dist/src/orchestration/applicationDeployHelpers.js +223 -0
  55. package/dist/src/orchestration/applicationDestroy.d.ts +14 -0
  56. package/dist/src/orchestration/applicationDestroy.js +131 -0
  57. package/dist/src/orchestration/builders/dockerBuilder.d.ts +17 -0
  58. package/dist/src/orchestration/builders/dockerBuilder.js +98 -0
  59. package/dist/src/orchestration/builders/frameworkRegistry.d.ts +23 -0
  60. package/dist/src/orchestration/builders/frameworkRegistry.js +1 -0
  61. package/dist/src/orchestration/builders/index.d.ts +4 -0
  62. package/dist/src/orchestration/builders/index.js +1 -0
  63. package/dist/src/orchestration/builders/openNextBuilder.d.ts +21 -0
  64. package/dist/src/orchestration/builders/openNextBuilder.js +144 -0
  65. package/dist/src/orchestration/cascadeDestroyHelpers.d.ts +30 -0
  66. package/dist/src/orchestration/cascadeDestroyHelpers.js +1 -0
  67. package/dist/src/orchestration/cascadeHelpers.d.ts +46 -0
  68. package/dist/src/orchestration/cascadeHelpers.js +160 -0
  69. package/dist/src/orchestration/contextHelpers.d.ts +47 -2
  70. package/dist/src/orchestration/contextHelpers.js +94 -1
  71. package/dist/src/orchestration/destroy.d.ts +13 -0
  72. package/dist/src/orchestration/destroy.js +67 -0
  73. package/dist/src/orchestration/detectionPipeline.d.ts +2 -11
  74. package/dist/src/orchestration/detectionPipeline.js +29 -10
  75. package/dist/src/orchestration/dockerBuildHelper.d.ts +10 -0
  76. package/dist/src/orchestration/dockerBuildHelper.js +49 -0
  77. package/dist/src/orchestration/dockerInterface.d.ts +4 -2
  78. package/dist/src/orchestration/index.d.ts +8 -1
  79. package/dist/src/orchestration/index.js +1 -3
  80. package/dist/src/orchestration/manifestSecretParser.d.ts +11 -0
  81. package/dist/src/orchestration/manifestSecretParser.js +1 -0
  82. package/dist/src/orchestration/openNextBuild.d.ts +28 -0
  83. package/dist/src/orchestration/openNextBuild.js +243 -0
  84. package/dist/src/orchestration/organisationDeploy.js +130 -228
  85. package/dist/src/orchestration/organisationDestroy.d.ts +24 -0
  86. package/dist/src/orchestration/organisationDestroy.js +189 -0
  87. package/dist/src/orchestration/organisationSetup.d.ts +6 -4
  88. package/dist/src/orchestration/organisationSetup.js +28 -8
  89. package/dist/src/orchestration/resolveOperation.js +68 -6
  90. package/dist/src/orchestration/serviceFactory.d.ts +4 -0
  91. package/dist/src/orchestration/serviceFactory.js +1 -16
  92. package/dist/src/orchestration/spawnHelpers.d.ts +47 -0
  93. package/dist/src/orchestration/spawnHelpers.js +1 -0
  94. package/dist/src/orchestration/stackCleanup.d.ts +39 -0
  95. package/dist/src/orchestration/stackCleanup.js +1 -0
  96. package/dist/src/orchestration/welcomeImageHelper.d.ts +15 -0
  97. package/dist/src/orchestration/welcomeImageHelper.js +64 -0
  98. package/dist/src/services/application/ApplicationStackService.d.ts +21 -30
  99. package/dist/src/services/application/ApplicationStackService.js +16 -234
  100. package/dist/src/services/application/applicationStackHelpers.d.ts +46 -0
  101. package/dist/src/services/application/applicationStackHelpers.js +248 -0
  102. package/dist/src/services/application/index.d.ts +1 -0
  103. package/dist/src/services/application/index.js +1 -1
  104. package/dist/src/services/index.d.ts +6 -0
  105. package/dist/src/services/index.js +1 -0
  106. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -67
  107. package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +10 -2
  108. package/dist/src/services/infrastructure/CdkCommandRunner.js +18 -15
  109. package/dist/src/services/infrastructure/CdkErrorFormatter.js +16 -194
  110. package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -41
  111. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -1
  112. package/dist/src/services/infrastructure/CdkOutputParser.js +2 -33
  113. package/dist/src/services/infrastructure/CdkProcessManager.d.ts +5 -0
  114. package/dist/src/services/infrastructure/CdkProcessManager.js +81 -47
  115. package/dist/src/services/infrastructure/CdkService.d.ts +7 -52
  116. package/dist/src/services/infrastructure/CdkService.js +41 -82
  117. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +50 -0
  118. package/dist/src/services/infrastructure/CdkServiceTypes.js +0 -0
  119. package/dist/src/services/infrastructure/CloudFormationService.js +9 -10
  120. package/dist/src/services/infrastructure/ICdkProcessManager.d.ts +27 -0
  121. package/dist/src/services/infrastructure/ICdkProcessManager.js +1 -0
  122. package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.d.ts +9 -0
  123. package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.js +1 -0
  124. package/dist/src/services/infrastructure/cdkServiceHelpers.d.ts +9 -0
  125. package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -0
  126. package/dist/src/services/infrastructure/constructMapEnrichment.d.ts +7 -0
  127. package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -0
  128. package/dist/src/services/infrastructure/index.d.ts +3 -1
  129. package/dist/src/services/infrastructure/index.js +1 -7
  130. package/dist/src/services/supporting/TemplateHashService.js +1 -1
  131. package/dist/src/services/supporting/helpers.js +1 -81
  132. package/dist/src/services/supporting/index.js +1 -3
  133. package/dist/src/steps/index.d.ts +1 -0
  134. package/dist/src/steps/index.js +1 -0
  135. package/dist/src/steps/stepRegistry.d.ts +71 -0
  136. package/dist/src/steps/stepRegistry.js +505 -0
  137. package/dist/src/types/FjallState.js +1 -118
  138. package/dist/src/types/ProgressEvent.js +1 -48
  139. package/dist/src/types/application/ApplicationServiceTypes.js +1 -30
  140. package/dist/src/types/application/index.js +1 -1
  141. package/dist/src/types/callbacks.d.ts +76 -4
  142. package/dist/src/types/callbacks.js +0 -1
  143. package/dist/src/types/constants.d.ts +2 -0
  144. package/dist/src/types/constants.js +1 -6
  145. package/dist/src/types/credentials.d.ts +1 -1
  146. package/dist/src/types/credentials.js +0 -1
  147. package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +5 -2
  148. package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
  149. package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
  150. package/dist/src/types/deployment/DeploymentTypes.js +0 -1
  151. package/dist/src/types/deployment/cloudformation.js +0 -1
  152. package/dist/src/types/deployment/index.d.ts +3 -1
  153. package/dist/src/types/deployment/index.js +1 -1
  154. package/dist/src/types/deployment/parallel.js +1 -10
  155. package/dist/src/types/deploymentEventSchema.d.ts +158 -0
  156. package/dist/src/types/deploymentEventSchema.js +1 -0
  157. package/dist/src/types/detection.d.ts +22 -0
  158. package/dist/src/types/detection.js +1 -0
  159. package/dist/src/types/entitlements.d.ts +31 -0
  160. package/dist/src/types/entitlements.js +0 -0
  161. package/dist/src/types/errors/CdkError.js +1 -20
  162. package/dist/src/types/errors/ServiceError.d.ts +2 -1
  163. package/dist/src/types/errors/ServiceError.js +1 -119
  164. package/dist/src/types/errors/index.d.ts +2 -0
  165. package/dist/src/types/errors/index.js +1 -0
  166. package/dist/src/types/events.d.ts +3 -9
  167. package/dist/src/types/events.js +0 -5
  168. package/dist/src/types/frameworkBuilder.d.ts +96 -0
  169. package/dist/src/types/frameworkBuilder.js +8 -0
  170. package/dist/src/types/index.d.ts +19 -4
  171. package/dist/src/types/index.js +1 -9
  172. package/dist/src/types/operations.d.ts +3 -2
  173. package/dist/src/types/operations.js +1 -285
  174. package/dist/src/types/orgConfig.d.ts +2 -10
  175. package/dist/src/types/orgConfig.js +0 -11
  176. package/dist/src/types/params.d.ts +61 -0
  177. package/dist/src/types/patternDetection.d.ts +14 -16
  178. package/dist/src/types/patternDetection.js +14 -18
  179. package/dist/src/types/patternTypes.d.ts +19 -0
  180. package/dist/src/types/patternTypes.js +1 -0
  181. package/dist/src/types/stepDefinitions.d.ts +163 -0
  182. package/dist/src/types/stepDefinitions.js +98 -0
  183. package/dist/src/types/validation.js +0 -1
  184. package/dist/src/util/dockerfileDetection.d.ts +5 -0
  185. package/dist/src/util/dockerfileDetection.js +1 -0
  186. package/dist/src/util/index.d.ts +4 -3
  187. package/dist/src/util/index.js +1 -3
  188. package/dist/src/util/sequencedCallbacks.d.ts +44 -0
  189. package/dist/src/util/sequencedCallbacks.js +1 -0
  190. package/package.json +49 -8
  191. package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +0 -32
  192. package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +0 -228
  193. package/dist/src/aws/utils/errors.d.ts +0 -26
  194. package/dist/src/aws/utils/errors.js +0 -59
  195. package/dist/src/util/fsHelpers.d.ts +0 -4
  196. package/dist/src/util/fsHelpers.js +0 -16
  197. package/dist/src/util/securityHelpers.d.ts +0 -31
  198. package/dist/src/util/securityHelpers.js +0 -124
  199. package/dist/src/util/singleton.d.ts +0 -2
  200. package/dist/src/util/singleton.js +0 -9
  201. package/dist/src/util/sleep.d.ts +0 -4
  202. 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,71 +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
- orgConfig?: string;
23
- }
24
- export interface CdkOptions {
25
- verbose?: boolean;
26
- noPrompt?: boolean;
27
- outputCallback?: (output: string) => void;
28
- errorCallback?: (error: string) => void;
29
- context?: CdkContext;
30
- timeout?: number;
31
- passThroughCDK?: boolean;
32
- stacks?: string[];
33
- noLookups?: boolean;
34
- useCdkOut?: boolean;
35
- credentials?: {
36
- accessKeyId: string;
37
- secretAccessKey: string;
38
- sessionToken?: string;
39
- };
40
- outputDir?: string;
41
- appDir?: string;
42
- cdkOutputLogger?: {
43
- writeCdkOutput(stream: "stdout" | "stderr", chunk: string): void;
44
- };
45
- }
46
- export interface CdkOutput {
47
- output?: string;
48
- exitCode?: number;
49
- }
50
- export interface CdkServiceOptions {
51
- eventLogWriterFactory?: EventLogWriterFactory;
52
- }
7
+ import type { CdkOptions, CdkOutput, CdkServiceOptions } from "./CdkServiceTypes.js";
8
+ export type { CdkContext, CdkOptions, CdkOutput, CdkServiceOptions } from "./CdkServiceTypes.js";
53
9
  export { type CheckDifferencesResult } from "./CdkCommandRunner.js";
54
10
  export declare class CdkService {
55
11
  private commandRunner;
56
12
  private eventMonitor;
57
13
  constructor(options?: CdkServiceOptions);
14
+ dispose(): void;
58
15
  checkDifferences(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CheckDifferencesResult, CdkError>>;
59
16
  deploy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
60
17
  destroy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
61
18
  runImport(path: string, resourceMappingFile?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
62
19
  synth(path: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
63
20
  bootstrap(accountId: string, region: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
64
- private resolveStackName;
65
- private buildDeploymentCdkContext;
66
21
  runCdkSynth(context: DeploymentContext, onOutput?: (chunk: string) => void): Promise<Result<StepOutput, string>>;
67
- 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>>;
68
23
  runCdkDiff(context: DeploymentContext, onOutput?: (chunk: string) => void): Promise<Result<StepOutput, string>>;
69
- runCdkDeploy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider): Promise<Result<StepOutput, string>>;
70
- 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>>;
71
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,47 +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
- orgConfig: context.orgConfig
74
- };
75
- }
76
47
  async runCdkSynth(context, onOutput) {
77
48
  const accountId = context.callerIdentity?.Account;
78
49
  try {
79
50
  const result = await this.synth(context.path, {
80
51
  outputCallback: onOutput,
81
- context: this.buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
52
+ context: buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
82
53
  });
83
54
  if (!result.success) {
84
55
  return failure(result.error || "Failed to synthesise CloudFormation template");
@@ -94,7 +65,7 @@ export class CdkService {
94
65
  return failure(`CDK synth failed: ${getErrorMessage(error)}`);
95
66
  }
96
67
  }
97
- async runCdkBootstrap(context, onOutput) {
68
+ async runCdkBootstrap(context, onOutput, credentials) {
98
69
  const accountId = context.callerIdentity?.Account;
99
70
  const region = context.region || DEFAULT_REGION;
100
71
  try {
@@ -106,7 +77,8 @@ export class CdkService {
106
77
  return failure(`Dependencies not installed. Please run 'npm install' in ${context.path} before deploying.`);
107
78
  }
108
79
  const result = await this.bootstrap(accountId, region, {
109
- outputCallback: onOutput
80
+ outputCallback: onOutput,
81
+ credentials
110
82
  });
111
83
  if (!result.success) {
112
84
  return failure(result.error || "Failed to bootstrap AWS environment");
@@ -123,7 +95,7 @@ export class CdkService {
123
95
  const result = await this.checkDifferences(context.path, undefined, {
124
96
  verbose: context.options?.verbose,
125
97
  outputCallback: onOutput,
126
- context: this.buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
98
+ context: buildDeploymentCdkContext(context, accountId, context.region || DEFAULT_REGION)
127
99
  });
128
100
  if (!result.success) {
129
101
  return failure(`CDK diff failed: ${result.error.message}`);
@@ -140,7 +112,7 @@ export class CdkService {
140
112
  return failure(`CDK diff failed: ${getErrorMessage(error)}`);
141
113
  }
142
114
  }
143
- async runCdkDeploy(context, stackPattern, onOutput, onResourceProgress, aws) {
115
+ async runCdkDeploy(context, stackPattern, onOutput, onResourceProgress, aws, credentials) {
144
116
  const accountId = context.callerIdentity?.Account;
145
117
  const region = context.region || DEFAULT_REGION;
146
118
  if (!accountId) {
@@ -149,23 +121,16 @@ export class CdkService {
149
121
  if (!aws) {
150
122
  return failure("AwsProvider is required for deployment monitoring.");
151
123
  }
124
+ // Read construct map from fjall-manifest.json (written during synth)
125
+ const enrichedOnResourceProgress = wrapWithConstructMapEnrichment(context.path, onResourceProgress);
152
126
  let cfnMonitor = null;
153
127
  let fallbackMonitoringTimeout;
154
128
  try {
155
- const targetStackName = this.resolveStackName(stackPattern, context) ??
156
- (() => {
157
- const deployType = context.deployType;
158
- if (deployType === "organisation" ||
159
- deployType === "platform" ||
160
- deployType === "account") {
161
- return getOrganisationStackName(deployType);
162
- }
163
- const appName = context.target || "app";
164
- return `${appName}Network`;
165
- })();
129
+ const targetStackName = resolveStackName(stackPattern, context) ??
130
+ getFallbackStackName(context);
166
131
  cfnMonitor = await this.eventMonitor.createEventMonitor("deploy", targetStackName, region, context, aws);
167
132
  if (onOutput) {
168
- onOutput(`Starting CloudFormation deployment of ${targetStackName}...\n`);
133
+ onOutput(maskSensitiveOutput(`Starting CloudFormation deployment of ${targetStackName}...\n`));
169
134
  onOutput(`Monitoring CloudFormation events (CDK process running in background)...\n`);
170
135
  }
171
136
  const state = {
@@ -174,7 +139,7 @@ export class CdkService {
174
139
  stackDetected: false,
175
140
  monitoringPromise: null
176
141
  };
177
- const enhancedOutputCallback = createEnhancedOutputCallback(state, onOutput, cfnMonitor, onResourceProgress);
142
+ const enhancedOutputCallback = createEnhancedOutputCallback(state, onOutput, cfnMonitor, enrichedOnResourceProgress);
178
143
  // Fall back to predicted stack name if actual name not detected in time
179
144
  fallbackMonitoringTimeout = setTimeout(() => {
180
145
  if (!state.stackDetected && cfnMonitor && !state.monitoringPromise) {
@@ -183,7 +148,7 @@ export class CdkService {
183
148
  stackDetected: state.stackDetected,
184
149
  hasOnResourceProgress: !!onResourceProgress
185
150
  });
186
- state.monitoringPromise = startStackMonitoring(cfnMonitor, targetStackName, onResourceProgress);
151
+ state.monitoringPromise = startStackMonitoring(cfnMonitor, targetStackName, enrichedOnResourceProgress);
187
152
  }
188
153
  }, STACK_DETECTION_FALLBACK_MS);
189
154
  const result = await this.deploy(context.path, targetStackName, {
@@ -191,37 +156,31 @@ export class CdkService {
191
156
  outputCallback: enhancedOutputCallback,
192
157
  useCdkOut: true,
193
158
  cdkOutputLogger: cfnMonitor?.getEventLogger() ?? undefined,
194
- context: this.buildDeploymentCdkContext(context, accountId, region)
159
+ context: buildDeploymentCdkContext(context, accountId, region),
160
+ credentials
195
161
  });
196
- clearTimeout(fallbackMonitoringTimeout);
197
- if (cfnMonitor) {
198
- logger.debug("CdkService", "CDK process exited, stopping monitor", {
199
- targetStackName,
200
- stackDetected: state.stackDetected,
201
- hadMonitoringPromise: !!state.monitoringPromise
202
- });
203
- cfnMonitor.stopMonitoring();
204
- }
205
162
  return analyseDeployOutput(state.cdkOutput, result, state.actualStackName);
206
163
  }
207
164
  catch (error) {
208
- clearTimeout(fallbackMonitoringTimeout);
209
- if (cfnMonitor) {
210
- cfnMonitor.stopMonitoring();
211
- }
212
165
  const errorMsg = `CDK deploy failed: ${getErrorMessage(error)}`;
213
166
  logger.error("CdkService", "CDK deployment exception", {
214
167
  error: errorMsg
215
168
  });
216
169
  return failure(errorMsg);
217
170
  }
171
+ finally {
172
+ clearTimeout(fallbackMonitoringTimeout);
173
+ if (cfnMonitor) {
174
+ cfnMonitor.stopMonitoring();
175
+ }
176
+ }
218
177
  }
219
- async runCdkDestroy(context, stackPattern, onOutput, onResourceProgress, aws, useCdkOut) {
178
+ async runCdkDestroy(context, stackPattern, onOutput, onResourceProgress, aws, useCdkOut, credentials) {
220
179
  const accountId = context.callerIdentity?.Account;
221
180
  const region = context.region || DEFAULT_REGION;
222
181
  let cfnMonitor = null;
223
182
  try {
224
- const targetStackName = this.resolveStackName(stackPattern, context);
183
+ const targetStackName = resolveStackName(stackPattern, context);
225
184
  if (accountId && targetStackName && aws) {
226
185
  cfnMonitor = await this.eventMonitor.createEventMonitor("destroy", targetStackName, region, context, aws);
227
186
  cfnMonitor.startMonitoring(targetStackName, (event) => {
@@ -235,20 +194,20 @@ export class CdkService {
235
194
  outputCallback: onOutput,
236
195
  useCdkOut,
237
196
  cdkOutputLogger: cfnMonitor?.getEventLogger() ?? undefined,
238
- context: this.buildDeploymentCdkContext(context, accountId, region, {
197
+ context: buildDeploymentCdkContext(context, accountId, region, {
239
198
  includeImageVersion: false
240
- })
199
+ }),
200
+ credentials
241
201
  });
242
- if (cfnMonitor) {
243
- cfnMonitor.stopMonitoring();
244
- }
245
202
  return analyseDestroyResult(result);
246
203
  }
247
204
  catch (error) {
205
+ return failure(`CDK destroy failed: ${getErrorMessage(error)}`);
206
+ }
207
+ finally {
248
208
  if (cfnMonitor) {
249
209
  cfnMonitor.stopMonitoring();
250
210
  }
251
- return failure(`CDK destroy failed: ${getErrorMessage(error)}`);
252
211
  }
253
212
  }
254
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);