@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
@@ -1,11 +1,11 @@
1
1
  import { success, failure } from "@fjall/generator";
2
- import { logger } from "@fjall/util";
3
- import { APPLICATION_STACKS, getApplicationDeployOrder, getApplicationStackName, getApplicationStepName, getApplicationStepId, PARALLEL_DEPLOY_GROUPS } from "../types/operations.js";
2
+ import { logger } from "@fjall/util/logger";
3
+ import { stubCallerIdentity } from "../types/deployment/index.js";
4
+ import { getApplicationDeployOrder, getApplicationStackName, getApplicationStepName, getApplicationStepId } from "../types/operations.js";
4
5
  import { CdkContextBuilder } from "../services/supporting/CdkContextBuilder.js";
5
- import { emitProgress } from "../services/supporting/helpers.js";
6
- import { buildParamsContext } from "./contextHelpers.js";
6
+ import { buildParamsContext, bootstrapOrFail } from "./contextHelpers.js";
7
7
  import { runDetectionPipeline } from "./detectionPipeline.js";
8
- import { detectPattern } from "../types/patternDetection.js";
8
+ import { getParallelPhase2Stacks, deployParallelPhase, deployStackSequential, runDockerPreCompute, deployAllStacks, createBuildCallbacks } from "./applicationDeployHelpers.js";
9
9
  /**
10
10
  * Core application deployment orchestration.
11
11
  *
@@ -21,15 +21,34 @@ export async function deployApplication(params, services, operation) {
21
21
  target: operation.appName,
22
22
  path: operation.path,
23
23
  region: services.awsProvider.getRegion(),
24
- ...buildParamsContext(params)
24
+ callerIdentity: stubCallerIdentity(services.awsProvider.getAccountId()),
25
+ ...buildParamsContext({
26
+ orgConfig: params.orgConfig,
27
+ identity: params.identity,
28
+ skipOidc: params.options?.skipOidc
29
+ })
25
30
  }, {
26
31
  verbose: options?.verbose,
27
32
  infraOnly: options?.infraOnly
28
33
  }, params.orgConfig);
34
+ // 1b. Resolve framework builder and run build (if applicable — runs before detection)
35
+ const resolved = services.frameworkRegistry.resolve({
36
+ appPath: operation.path
37
+ });
38
+ let plan;
39
+ if (resolved) {
40
+ plan = resolved.builder.plan({ appPath: operation.path }, resolved.detection);
41
+ const buildCallbacks = createBuildCallbacks(callbacks);
42
+ const buildResult = await resolved.builder.build(operation.path, plan, buildCallbacks, { skipBuild: options?.skipBuild, infraOnly: options?.infraOnly });
43
+ if (!buildResult.success) {
44
+ callbacks.onError?.(buildResult.error);
45
+ return failure(buildResult.error);
46
+ }
47
+ }
29
48
  // deployOnly: skip detection pipeline, deploy all stacks unconditionally
30
49
  if (options?.deployOnly) {
31
50
  callbacks.onLog?.("Deploy-only mode — skipping change detection", "info");
32
- return deployAllStacks(params, services, operation, context, startTime);
51
+ return deployAllStacks(params, services, operation, context, startTime, plan);
33
52
  }
34
53
  // 2. Run detection pipeline
35
54
  callbacks.onLog?.("Analysing infrastructure…", "info");
@@ -39,6 +58,12 @@ export async function deployApplication(params, services, operation) {
39
58
  return failure(detectionResult.error);
40
59
  }
41
60
  const detection = detectionResult.data;
61
+ // 2b. Notify callers of detection results (resource flags, pattern, etc.)
62
+ // Awaited so callers can run post-detection hooks (e.g. secrets validation).
63
+ await callbacks.onDetectionComplete?.({
64
+ ...detection,
65
+ builderName: resolved?.builder.name ?? "unknown"
66
+ });
42
67
  // 3. Early exit if no differences
43
68
  if (!detection.hasDifferences && !options?.force) {
44
69
  callbacks.onLog?.("No infrastructure changes detected", "info");
@@ -49,20 +74,16 @@ export async function deployApplication(params, services, operation) {
49
74
  });
50
75
  }
51
76
  // 4. Bootstrap AWS environment
52
- callbacks.onCDKBootstrap?.("bootstrapping");
53
- const bootstrapResult = await services.cdkService.runCdkBootstrap(context, (chunk) => callbacks.onOutput?.(chunk));
54
- if (!bootstrapResult.success) {
55
- callbacks.onCDKBootstrap?.("failed");
56
- const error = new Error(`Bootstrap failed: ${bootstrapResult.error}`);
57
- callbacks.onError?.(error);
58
- return failure(error);
59
- }
60
- callbacks.onCDKBootstrap?.("complete");
61
- // 5. Determine deploy order
62
- const deployOrder = getApplicationDeployOrder({
63
- pattern: detection.pattern,
64
- resources: detection.resources
65
- });
77
+ const bsResult = await bootstrapOrFail(services, context, callbacks);
78
+ if (!bsResult.success)
79
+ return bsResult;
80
+ // 5. Determine deploy order (prefer plan from registry, fall back to legacy)
81
+ const deployOrder = plan
82
+ ? plan.deployOrder
83
+ : getApplicationDeployOrder({
84
+ pattern: detection.pattern,
85
+ resources: detection.resources
86
+ });
66
87
  const totalSteps = deployOrder.length;
67
88
  // 6. Deploy stacks
68
89
  const allOutputs = {};
@@ -90,14 +111,10 @@ export async function deployApplication(params, services, operation) {
90
111
  i += parallelStacks.length - 1;
91
112
  continue;
92
113
  }
93
- // Docker build before Compute stack
94
- if (stack === APPLICATION_STACKS.COMPUTE &&
95
- detection.hasDockerfile &&
96
- params.dockerProvider) {
97
- const dockerResult = await runDockerBuild(params, services, operation, callbacks);
98
- if (!dockerResult.success)
99
- return failure(dockerResult.error);
100
- }
114
+ // Docker operations before Compute stack
115
+ const dockerFailed = await runDockerPreCompute(stack, params, services, operation, callbacks, detection.hasDockerfile);
116
+ if (dockerFailed)
117
+ return dockerFailed;
101
118
  // Sequential deployment
102
119
  const deployResult = await deployStackSequential(stack, services, context, callbacks, i, totalSteps, allOutputs);
103
120
  if (!deployResult.success)
@@ -130,198 +147,3 @@ export async function deployApplication(params, services, operation) {
130
147
  durationMs: Date.now() - startTime
131
148
  });
132
149
  }
133
- /**
134
- * Identify Phase 2 stacks (Storage, Messaging, Database) that are consecutive
135
- * in the deploy order and have changes. Returns them for parallel deployment
136
- * only if there are at least 2.
137
- */
138
- function getParallelPhase2Stacks(deployOrder, currentIndex, stackChanges, force) {
139
- const phase2Set = new Set(PARALLEL_DEPLOY_GROUPS.PHASE_2.stacks);
140
- const candidates = [];
141
- for (let j = currentIndex; j < deployOrder.length; j++) {
142
- if (phase2Set.has(deployOrder[j])) {
143
- candidates.push(deployOrder[j]);
144
- }
145
- else {
146
- break;
147
- }
148
- }
149
- if (candidates.length < 2)
150
- return [];
151
- // Only include stacks that actually have changes (unless forced)
152
- if (!force) {
153
- const withChanges = candidates.filter((stack) => {
154
- // stackChanges is keyed by full stack name but we only have the type here;
155
- // since we don't know the app name, check all entries ending with the stack type
156
- for (const [key, changed] of stackChanges) {
157
- if (key.endsWith(stack) && !changed)
158
- return false;
159
- }
160
- return true;
161
- });
162
- return withChanges.length >= 2 ? withChanges : [];
163
- }
164
- return candidates;
165
- }
166
- /**
167
- * Deploy a group of stacks in parallel, reporting step events for each.
168
- */
169
- async function deployParallelPhase(stacks, operation, services, context, callbacks, startIndex, totalSteps, detection, allOutputs, deployedHashes) {
170
- // Emit step start for each parallel stack
171
- for (let k = 0; k < stacks.length; k++) {
172
- const stack = stacks[k];
173
- const stepId = getApplicationStepId(stack, "deploy");
174
- const stepName = getApplicationStepName(stack, "deploy");
175
- callbacks.onStepStart?.(stepId, stepName, startIndex + k, totalSteps);
176
- }
177
- emitProgress(callbacks, "Deploying infrastructure in parallel…");
178
- const parallelResult = await services.stackService.deployStacksInParallel(stacks, context, {
179
- onOutput: (chunk) => callbacks.onOutput?.(chunk),
180
- onResourceProgress: (event) => callbacks.onResourceProgress?.(event),
181
- onStackComplete: (stack, stackSuccess, _duration, error) => {
182
- const stepId = getApplicationStepId(stack, "deploy");
183
- const stepName = getApplicationStepName(stack, "deploy");
184
- const idx = startIndex + stacks.indexOf(stack);
185
- callbacks.onStepComplete?.(stepId, stepName, stackSuccess ? "completed" : "error", idx, totalSteps);
186
- if (!stackSuccess && error) {
187
- callbacks.onError?.(error);
188
- }
189
- }
190
- });
191
- if (!parallelResult.success) {
192
- return failure(parallelResult.error);
193
- }
194
- // Check for any failures
195
- const failures = parallelResult.data.filter((r) => !r.success);
196
- if (failures.length > 0) {
197
- const failedStacks = failures.map((f) => f.stack).join(", ");
198
- const errorDetails = failures
199
- .map((f) => `${f.stack}: ${f.error?.message ?? "Unknown error"}`)
200
- .join("\n");
201
- return failure(new Error(`Failed to deploy stacks: ${failedStacks}\n\n${errorDetails}`));
202
- }
203
- // Collect outputs and track hashes for successful parallel stacks
204
- for (const result of parallelResult.data) {
205
- if (result.success && result.outputs) {
206
- for (const [key, value] of Object.entries(result.outputs)) {
207
- allOutputs[key] = String(value);
208
- }
209
- }
210
- const stackName = getApplicationStackName(operation.appName, result.stack);
211
- const hash = detection.currentHashes.get(stackName);
212
- if (hash) {
213
- deployedHashes.set(stackName, hash);
214
- }
215
- }
216
- return success(undefined);
217
- }
218
- /**
219
- * Run Docker build and push before deploying the Compute stack.
220
- * Initialises ECR if needed, then builds and pushes the image.
221
- */
222
- async function runDockerBuild(params, services, operation, callbacks) {
223
- const dockerProvider = params.dockerProvider;
224
- if (!dockerProvider)
225
- return success(undefined);
226
- const accountId = services.awsProvider.getAccountId();
227
- if (!accountId) {
228
- callbacks.onLog?.("Skipping Docker build — account ID not available", "warn");
229
- return success(undefined);
230
- }
231
- const region = services.awsProvider.getRegion();
232
- // Initialise ECR repository (non-fatal — CDK creates it if needed)
233
- callbacks.onLog?.("Initialising ECR repository…", "info");
234
- const ecrResult = await dockerProvider.initialiseECR({
235
- appName: operation.appName,
236
- region,
237
- accountId
238
- });
239
- if (!ecrResult.success) {
240
- callbacks.onLog?.(`ECR initialisation failed: ${ecrResult.error.message}`, "warn");
241
- }
242
- // Build and push Docker image
243
- callbacks.onLog?.("Building and pushing Docker image…", "info");
244
- const buildResult = await dockerProvider.buildAndPush({
245
- appName: operation.appName,
246
- appPath: operation.path,
247
- region,
248
- accountId
249
- });
250
- if (!buildResult.success) {
251
- callbacks.onError?.(buildResult.error);
252
- return failure(buildResult.error);
253
- }
254
- callbacks.onLog?.(`Docker image pushed: ${buildResult.data.imageUri}`, "info");
255
- return success(undefined);
256
- }
257
- /**
258
- * Deploy a single stack with step lifecycle events and output collection.
259
- */
260
- async function deployStackSequential(stack, services, context, callbacks, stepIndex, totalSteps, allOutputs) {
261
- const stepId = getApplicationStepId(stack, "deploy");
262
- const stepName = getApplicationStepName(stack, "deploy");
263
- callbacks.onStepStart?.(stepId, stepName, stepIndex, totalSteps);
264
- const result = await services.stackService.deployStack(stack, context, {
265
- onOutput: (chunk) => callbacks.onOutput?.(chunk),
266
- onResourceProgress: (event) => callbacks.onResourceProgress?.(event)
267
- });
268
- if (!result.success) {
269
- callbacks.onStepComplete?.(stepId, stepName, "error", stepIndex, totalSteps);
270
- callbacks.onError?.(result.error);
271
- return failure(result.error);
272
- }
273
- callbacks.onStepComplete?.(stepId, stepName, "completed", stepIndex, totalSteps);
274
- if (result.data.outputs) {
275
- for (const [key, value] of Object.entries(result.data.outputs)) {
276
- allOutputs[key] = String(value);
277
- }
278
- }
279
- return success(undefined);
280
- }
281
- /**
282
- * Deploy all stacks unconditionally (deployOnly mode).
283
- * Skips CDK synth/hash comparison — uses filesystem pattern detection only
284
- * to determine deploy order, then deploys every stack sequentially.
285
- */
286
- async function deployAllStacks(params, services, operation, context, startTime) {
287
- const { callbacks } = params;
288
- // Lightweight pattern detection (filesystem only, no CDK synth)
289
- const { pattern } = detectPattern(operation.path);
290
- const deployOrder = getApplicationDeployOrder({ pattern });
291
- const totalSteps = deployOrder.length;
292
- // Bootstrap
293
- callbacks.onCDKBootstrap?.("bootstrapping");
294
- const bootstrapResult = await services.cdkService.runCdkBootstrap(context, (chunk) => callbacks.onOutput?.(chunk));
295
- if (!bootstrapResult.success) {
296
- callbacks.onCDKBootstrap?.("failed");
297
- const error = new Error(`Bootstrap failed: ${bootstrapResult.error}`);
298
- callbacks.onError?.(error);
299
- return failure(error);
300
- }
301
- callbacks.onCDKBootstrap?.("complete");
302
- // Deploy all stacks sequentially
303
- const allOutputs = {};
304
- for (let i = 0; i < deployOrder.length; i++) {
305
- const stack = deployOrder[i];
306
- // Docker build before Compute stack
307
- if (stack === APPLICATION_STACKS.COMPUTE && params.dockerProvider) {
308
- const dockerResult = await runDockerBuild(params, services, operation, callbacks);
309
- if (!dockerResult.success)
310
- return failure(dockerResult.error);
311
- }
312
- const deployResult = await deployStackSequential(stack, services, context, callbacks, i, totalSteps, allOutputs);
313
- if (!deployResult.success)
314
- return failure(deployResult.error);
315
- }
316
- // Resolve website URL
317
- const websiteUrl = await services.stackService.resolveWebsiteUrl(operation.appName);
318
- if (websiteUrl) {
319
- allOutputs.websiteUrl = websiteUrl;
320
- }
321
- return success({
322
- target: operation.appName,
323
- deploymentType: "application",
324
- outputs: Object.keys(allOutputs).length > 0 ? allOutputs : undefined,
325
- durationMs: Date.now() - startTime
326
- });
327
- }
@@ -0,0 +1,39 @@
1
+ import { type Result } from "@fjall/generator";
2
+ import type { DeployParams, DeployResult } from "../types/params.js";
3
+ import type { DeploymentContext } from "../types/deployment/DeploymentTypes.js";
4
+ import type { DeployCallbacks } from "../types/callbacks.js";
5
+ import type { ApplicationOperation, ApplicationStack } from "../types/operations.js";
6
+ import type { DeployServices } from "./serviceFactory.js";
7
+ import type { DetectionResult } from "./detectionPipeline.js";
8
+ import type { BuildCallbacks, BuildPlan } from "../types/frameworkBuilder.js";
9
+ /**
10
+ * Identify Phase 2 stacks (Storage, Messaging, Database) that are consecutive
11
+ * in the deploy order and have changes. Returns them for parallel deployment
12
+ * only if there are at least 2.
13
+ */
14
+ export declare function getParallelPhase2Stacks(deployOrder: readonly ApplicationStack[], currentIndex: number, stackChanges: Map<string, boolean>, force?: boolean): ApplicationStack[];
15
+ /**
16
+ * Deploy a group of stacks in parallel, reporting step events for each.
17
+ */
18
+ export declare function deployParallelPhase(stacks: ApplicationStack[], operation: ApplicationOperation, services: DeployServices, context: DeploymentContext, callbacks: DeployCallbacks, startIndex: number, totalSteps: number, detection: DetectionResult, allOutputs: Record<string, string>, deployedHashes: Map<string, string>): Promise<Result<void>>;
19
+ /**
20
+ * Deploy a single stack with step lifecycle events and output collection.
21
+ */
22
+ export declare function deployStackSequential(stack: ApplicationStack, services: DeployServices, context: DeploymentContext, callbacks: DeployCallbacks, stepIndex: number, totalSteps: number, allOutputs: Record<string, string>): Promise<Result<void>>;
23
+ /**
24
+ * Run Docker build/push or welcome image setup before the Compute stack.
25
+ * Returns a failure Result if Docker operations fail, or null if not applicable/successful.
26
+ */
27
+ export declare function runDockerPreCompute(stack: ApplicationStack, params: DeployParams, services: DeployServices, operation: ApplicationOperation, callbacks: DeployCallbacks, hasDockerfileOnDisk: boolean): Promise<Result<DeployResult> | null>;
28
+ /**
29
+ * Deploy all stacks unconditionally (deployOnly mode).
30
+ * Skips CDK synth/hash comparison — uses the framework registry to determine
31
+ * deploy order, then deploys every stack sequentially.
32
+ */
33
+ export declare function deployAllStacks(params: DeployParams, services: DeployServices, operation: ApplicationOperation, context: DeploymentContext, startTime: number, plan?: BuildPlan): Promise<Result<DeployResult>>;
34
+ /**
35
+ * Bridge DeployCallbacks to BuildCallbacks.
36
+ * Fires both legacy OpenNext-specific callbacks and generic build callbacks
37
+ * for backwards compatibility during the transition period.
38
+ */
39
+ export declare function createBuildCallbacks(callbacks: DeployCallbacks): BuildCallbacks;
@@ -0,0 +1,223 @@
1
+ import { success, failure } from "@fjall/generator";
2
+ import { APPLICATION_STACKS, getApplicationDeployOrder, getApplicationStackName, getApplicationStepName, getApplicationStepId, PARALLEL_DEPLOY_GROUPS } from "../types/operations.js";
3
+ import { emitProgress } from "../services/supporting/helpers.js";
4
+ import { bootstrapOrFail, forwardOutput, forwardResourceProgress } from "./contextHelpers.js";
5
+ import { hasDockerfile } from "../util/dockerfileDetection.js";
6
+ import { runDockerBuild } from "./dockerBuildHelper.js";
7
+ import { runWelcomeImageSetup } from "./welcomeImageHelper.js";
8
+ /**
9
+ * Identify Phase 2 stacks (Storage, Messaging, Database) that are consecutive
10
+ * in the deploy order and have changes. Returns them for parallel deployment
11
+ * only if there are at least 2.
12
+ */
13
+ export function getParallelPhase2Stacks(deployOrder, currentIndex, stackChanges, force) {
14
+ const phase2Set = new Set(PARALLEL_DEPLOY_GROUPS.PHASE_2.stacks);
15
+ const candidates = [];
16
+ for (let j = currentIndex; j < deployOrder.length; j++) {
17
+ if (phase2Set.has(deployOrder[j])) {
18
+ candidates.push(deployOrder[j]);
19
+ }
20
+ else {
21
+ break;
22
+ }
23
+ }
24
+ if (candidates.length < 2)
25
+ return [];
26
+ // Only include stacks that actually have changes (unless forced)
27
+ if (!force) {
28
+ const withChanges = candidates.filter((stack) => {
29
+ // stackChanges is keyed by full stack name but we only have the type here;
30
+ // since we don't know the app name, check all entries ending with the stack type
31
+ for (const [key, changed] of stackChanges) {
32
+ if (key.endsWith(stack) && !changed)
33
+ return false;
34
+ }
35
+ return true;
36
+ });
37
+ return withChanges.length >= 2 ? withChanges : [];
38
+ }
39
+ return candidates;
40
+ }
41
+ /**
42
+ * Deploy a group of stacks in parallel, reporting step events for each.
43
+ */
44
+ export async function deployParallelPhase(stacks, operation, services, context, callbacks, startIndex, totalSteps, detection, allOutputs, deployedHashes) {
45
+ // Emit step start for each parallel stack
46
+ for (let k = 0; k < stacks.length; k++) {
47
+ const stack = stacks[k];
48
+ const stepId = getApplicationStepId(stack, "deploy");
49
+ const stepName = getApplicationStepName(stack, "deploy");
50
+ callbacks.onStepStart?.(stepId, stepName, startIndex + k, totalSteps);
51
+ }
52
+ emitProgress(callbacks, "Deploying infrastructure in parallel…");
53
+ callbacks.onParallelPhaseStart?.(stacks, "Storage and database resources (parallel)");
54
+ const parallelResult = await services.stackService.deployStacksInParallel(stacks, context, {
55
+ onOutput: forwardOutput(callbacks),
56
+ onResourceProgress: (event, stackId) => {
57
+ callbacks.onResourceProgress?.(event);
58
+ if (stackId) {
59
+ callbacks.onParallelStackResourceProgress?.(stackId, event);
60
+ }
61
+ },
62
+ onStackComplete: (stack, stackSuccess, _duration, error) => {
63
+ const stepId = getApplicationStepId(stack, "deploy");
64
+ const stepName = getApplicationStepName(stack, "deploy");
65
+ const idx = startIndex + stacks.indexOf(stack);
66
+ callbacks.onStepComplete?.(stepId, stepName, stackSuccess ? "completed" : "error", idx, totalSteps);
67
+ if (!stackSuccess && error) {
68
+ callbacks.onError?.(error);
69
+ }
70
+ }
71
+ });
72
+ if (!parallelResult.success) {
73
+ callbacks.onParallelPhaseComplete?.([]);
74
+ return failure(parallelResult.error);
75
+ }
76
+ // Check for any failures
77
+ const failures = parallelResult.data.filter((r) => !r.success);
78
+ callbacks.onParallelPhaseComplete?.(parallelResult.data.map((r) => ({
79
+ stack: r.stack,
80
+ success: r.success,
81
+ error: r.error
82
+ })));
83
+ if (failures.length > 0) {
84
+ const failedStacks = failures.map((f) => f.stack).join(", ");
85
+ const errorDetails = failures
86
+ .map((f) => `${f.stack}: ${f.error?.message ?? "Unknown error"}`)
87
+ .join("\n");
88
+ return failure(new Error(`Failed to deploy stacks: ${failedStacks}\n\n${errorDetails}`));
89
+ }
90
+ // Collect outputs and track hashes for successful parallel stacks
91
+ for (const result of parallelResult.data) {
92
+ if (result.success && result.outputs) {
93
+ for (const [key, value] of Object.entries(result.outputs)) {
94
+ allOutputs[key] = String(value);
95
+ }
96
+ }
97
+ const stackName = getApplicationStackName(operation.appName, result.stack);
98
+ const hash = detection.currentHashes.get(stackName);
99
+ if (hash) {
100
+ deployedHashes.set(stackName, hash);
101
+ }
102
+ }
103
+ return success(undefined);
104
+ }
105
+ /**
106
+ * Deploy a single stack with step lifecycle events and output collection.
107
+ */
108
+ export async function deployStackSequential(stack, services, context, callbacks, stepIndex, totalSteps, allOutputs) {
109
+ const stepId = getApplicationStepId(stack, "deploy");
110
+ const stepName = getApplicationStepName(stack, "deploy");
111
+ callbacks.onStepStart?.(stepId, stepName, stepIndex, totalSteps);
112
+ const result = await services.stackService.deployStack(stack, context, {
113
+ onOutput: forwardOutput(callbacks),
114
+ onResourceProgress: forwardResourceProgress(callbacks)
115
+ });
116
+ if (!result.success) {
117
+ callbacks.onStepComplete?.(stepId, stepName, "error", stepIndex, totalSteps);
118
+ callbacks.onError?.(result.error);
119
+ return failure(result.error);
120
+ }
121
+ callbacks.onStepComplete?.(stepId, stepName, "completed", stepIndex, totalSteps);
122
+ if (result.data.outputs) {
123
+ for (const [key, value] of Object.entries(result.data.outputs)) {
124
+ allOutputs[key] = String(value);
125
+ }
126
+ }
127
+ return success(undefined);
128
+ }
129
+ /**
130
+ * Run Docker build/push or welcome image setup before the Compute stack.
131
+ * Returns a failure Result if Docker operations fail, or null if not applicable/successful.
132
+ */
133
+ export async function runDockerPreCompute(stack, params, services, operation, callbacks, hasDockerfileOnDisk) {
134
+ if (stack !== APPLICATION_STACKS.COMPUTE || !params.dockerProvider)
135
+ return null;
136
+ if (hasDockerfileOnDisk) {
137
+ const dockerResult = await runDockerBuild(params, services, operation, callbacks);
138
+ if (!dockerResult.success)
139
+ return failure(dockerResult.error);
140
+ }
141
+ else {
142
+ const ecrInitResult = await runWelcomeImageSetup(params, services, operation, callbacks);
143
+ if (!ecrInitResult.success)
144
+ return failure(ecrInitResult.error);
145
+ }
146
+ return null;
147
+ }
148
+ /**
149
+ * Deploy all stacks unconditionally (deployOnly mode).
150
+ * Skips CDK synth/hash comparison — uses the framework registry to determine
151
+ * deploy order, then deploys every stack sequentially.
152
+ */
153
+ export async function deployAllStacks(params, services, operation, context, startTime, plan) {
154
+ const { callbacks } = params;
155
+ // Use plan from registry if available, otherwise resolve now
156
+ let deployOrder;
157
+ if (plan) {
158
+ deployOrder = plan.deployOrder;
159
+ }
160
+ else {
161
+ const resolved = services.frameworkRegistry.resolve({
162
+ appPath: operation.path
163
+ });
164
+ if (resolved) {
165
+ const freshPlan = resolved.builder.plan({ appPath: operation.path }, resolved.detection);
166
+ deployOrder = freshPlan.deployOrder;
167
+ }
168
+ else {
169
+ deployOrder = getApplicationDeployOrder();
170
+ }
171
+ }
172
+ const totalSteps = deployOrder.length;
173
+ // Bootstrap
174
+ const bsResult = await bootstrapOrFail(services, context, callbacks);
175
+ if (!bsResult.success)
176
+ return bsResult;
177
+ // Deploy all stacks sequentially
178
+ const allOutputs = {};
179
+ for (let i = 0; i < deployOrder.length; i++) {
180
+ const stack = deployOrder[i];
181
+ // Docker operations before Compute stack
182
+ const dockerFailed = await runDockerPreCompute(stack, params, services, operation, callbacks, hasDockerfile(operation.path));
183
+ if (dockerFailed)
184
+ return dockerFailed;
185
+ const deployResult = await deployStackSequential(stack, services, context, callbacks, i, totalSteps, allOutputs);
186
+ if (!deployResult.success)
187
+ return failure(deployResult.error);
188
+ }
189
+ // Resolve website URL
190
+ const websiteUrl = await services.stackService.resolveWebsiteUrl(operation.appName);
191
+ if (websiteUrl) {
192
+ allOutputs.websiteUrl = websiteUrl;
193
+ }
194
+ return success({
195
+ target: operation.appName,
196
+ deploymentType: "application",
197
+ outputs: Object.keys(allOutputs).length > 0 ? allOutputs : undefined,
198
+ durationMs: Date.now() - startTime
199
+ });
200
+ }
201
+ /**
202
+ * Bridge DeployCallbacks to BuildCallbacks.
203
+ * Fires both legacy OpenNext-specific callbacks and generic build callbacks
204
+ * for backwards compatibility during the transition period.
205
+ */
206
+ export function createBuildCallbacks(callbacks) {
207
+ return {
208
+ onBuildStart: (builderName) => {
209
+ callbacks.onOpenNextBuildStart?.();
210
+ callbacks.onLog?.(`${builderName} build started`, "info");
211
+ },
212
+ onBuildProgress: (_builderName, message) => {
213
+ callbacks.onOpenNextProgress?.(message);
214
+ },
215
+ onBuildComplete: (builderName) => {
216
+ callbacks.onOpenNextBuildComplete?.();
217
+ callbacks.onLog?.(`${builderName} build complete`, "info");
218
+ },
219
+ onBuildError: (_builderName, error) => {
220
+ callbacks.onOpenNextBuildError?.(error);
221
+ }
222
+ };
223
+ }
@@ -0,0 +1,14 @@
1
+ import { type Result } from "@fjall/generator";
2
+ import type { DestroyParams, DestroyResult } from "../types/params.js";
3
+ import type { ApplicationOperation } from "../types/operations.js";
4
+ import type { DeployServices } from "./serviceFactory.js";
5
+ /**
6
+ * Core application destruction orchestration.
7
+ *
8
+ * Detects application pattern, determines destroy order (reverse of deploy),
9
+ * then destroys stacks sequentially or in parallel groups. Delegates all
10
+ * CDK operations to the ApplicationStackService.destroyAllStacks() method,
11
+ * which handles "stack doesn't exist" → success conversion and parallel
12
+ * phase orchestration for OpenNext patterns.
13
+ */
14
+ export declare function destroyApplication(params: DestroyParams, services: DeployServices, operation: ApplicationOperation): Promise<Result<DestroyResult>>;