@fjall/deploy-core 0.94.1 → 0.95.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 (54) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/organisations/accounts.js +1 -99
  3. package/dist/src/aws/organisations/backup.js +1 -30
  4. package/dist/src/aws/organisations/costAllocation.js +1 -28
  5. package/dist/src/aws/organisations/delegatedAdmin.js +3 -43
  6. package/dist/src/aws/organisations/identityCentre.js +1 -23
  7. package/dist/src/aws/organisations/ipam.js +1 -20
  8. package/dist/src/aws/organisations/organisation.js +1 -103
  9. package/dist/src/aws/organisations/organisationalUnits.js +1 -239
  10. package/dist/src/aws/organisations/policies.js +1 -37
  11. package/dist/src/aws/organisations/ram.js +1 -19
  12. package/dist/src/aws/organisations/serviceAccess.js +1 -44
  13. package/dist/src/aws/organisations/trustedAccess.js +1 -19
  14. package/dist/src/aws/utils/regions.js +1 -1
  15. package/dist/src/index.js +1 -65
  16. package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +1 -78
  17. package/dist/src/orchestration/activeDeploymentGuard.js +5 -39
  18. package/dist/src/orchestration/applicationDeploy.js +1 -149
  19. package/dist/src/orchestration/applicationDeployHelpers.js +4 -223
  20. package/dist/src/orchestration/applicationDestroy.js +1 -131
  21. package/dist/src/orchestration/builders/dockerBuilder.js +1 -98
  22. package/dist/src/orchestration/builders/openNextBuilder.js +1 -144
  23. package/dist/src/orchestration/cascadeHelpers.js +1 -160
  24. package/dist/src/orchestration/contextHelpers.js +1 -107
  25. package/dist/src/orchestration/deploy.js +1 -42
  26. package/dist/src/orchestration/destroy.js +1 -67
  27. package/dist/src/orchestration/detectionPipeline.js +1 -84
  28. package/dist/src/orchestration/dockerBuildHelper.js +1 -49
  29. package/dist/src/orchestration/dockerInterface.js +0 -1
  30. package/dist/src/orchestration/domainInterface.js +0 -1
  31. package/dist/src/orchestration/openNextBuild.js +3 -243
  32. package/dist/src/orchestration/organisationDeploy.js +3 -284
  33. package/dist/src/orchestration/organisationDestroy.js +3 -189
  34. package/dist/src/orchestration/organisationSetup.js +1 -247
  35. package/dist/src/orchestration/resolveOperation.js +1 -123
  36. package/dist/src/orchestration/welcomeImageHelper.js +1 -64
  37. package/dist/src/services/application/ApplicationStackService.js +1 -218
  38. package/dist/src/services/application/applicationStackHelpers.js +4 -248
  39. package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -244
  40. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -125
  41. package/dist/src/services/infrastructure/CdkProcessManager.js +3 -278
  42. package/dist/src/services/infrastructure/CdkService.js +3 -213
  43. package/dist/src/services/infrastructure/CloudFormationService.js +1 -248
  44. package/dist/src/services/infrastructure/ICdkProcessManager.js +0 -1
  45. package/dist/src/services/supporting/CdkContextBuilder.js +1 -44
  46. package/dist/src/services/supporting/TemplateHashService.js +1 -152
  47. package/dist/src/steps/stepRegistry.js +1 -505
  48. package/dist/src/types/apiClient.js +0 -1
  49. package/dist/src/types/detection.js +0 -1
  50. package/dist/src/types/frameworkBuilder.js +0 -8
  51. package/dist/src/types/params.js +0 -1
  52. package/dist/src/types/patternDetection.js +1 -88
  53. package/dist/src/types/stepDefinitions.js +1 -98
  54. package/package.json +4 -4
@@ -1,152 +1 @@
1
- /**
2
- * TemplateHashService - Hash-based change detection for CDK templates
3
- *
4
- * Replaces slow CDK diff with fast local hash comparison.
5
- * Uses SHA-256 hashing of synthesised templates.
6
- */
7
- import { createHash } from "crypto";
8
- import { readFile, readdir } from "fs/promises";
9
- import { join, basename } from "path";
10
- import { fileExists } from "@fjall/util/fsHelpers";
11
- import { success, failure } from "@fjall/generator";
12
- import { BaseServiceError } from "../../types/errors/ServiceError.js";
13
- import { readStateFile, writeStateFile, createEmptyState, updateTemplateHash, getStateFilePath } from "../../types/FjallState.js";
14
- /**
15
- * TemplateHash-specific error types
16
- */
17
- export class TemplateHashError extends BaseServiceError {
18
- errorType;
19
- constructor(message, errorType, details, recoverable = false) {
20
- super(`TEMPLATE_HASH_${errorType.toUpperCase()}`, message, details, recoverable);
21
- this.errorType = errorType;
22
- }
23
- }
24
- /** CDK template file extension */
25
- const TEMPLATE_EXTENSION = ".template.json";
26
- /**
27
- * Template Hash Service - provides hash-based change detection
28
- */
29
- export class TemplateHashService {
30
- /**
31
- * Compute SHA-256 hash of a template file
32
- */
33
- async computeTemplateHash(templatePath) {
34
- try {
35
- const content = await readFile(templatePath, "utf-8");
36
- const normalised = JSON.stringify(JSON.parse(content));
37
- const hash = createHash("sha256").update(normalised).digest("hex");
38
- return success(hash);
39
- }
40
- catch (error) {
41
- return failure(new TemplateHashError(`Failed to hash template: ${templatePath}`, "hash_failed", { path: templatePath, error }));
42
- }
43
- }
44
- /**
45
- * Get hashes for all templates in cdk.out directory.
46
- * Returns map of stack name to hash.
47
- */
48
- async getTemplateHashes(cdkOutPath) {
49
- if (!(await fileExists(cdkOutPath))) {
50
- return failure(new TemplateHashError(`CDK output directory not found: ${cdkOutPath}`, "cdk_out_not_found", { path: cdkOutPath }));
51
- }
52
- try {
53
- const files = await readdir(cdkOutPath);
54
- const templateFiles = files.filter((f) => f.endsWith(TEMPLATE_EXTENSION));
55
- const hashes = new Map();
56
- for (const file of templateFiles) {
57
- const stackName = basename(file, TEMPLATE_EXTENSION);
58
- const templatePath = join(cdkOutPath, file);
59
- const hashResult = await this.computeTemplateHash(templatePath);
60
- if (!hashResult.success) {
61
- return failure(hashResult.error);
62
- }
63
- hashes.set(stackName, hashResult.data);
64
- }
65
- return success(hashes);
66
- }
67
- catch (error) {
68
- return failure(new TemplateHashError(`Failed to read CDK output directory: ${cdkOutPath}`, "read_failed", { path: cdkOutPath, error }));
69
- }
70
- }
71
- /**
72
- * Compare current template hashes with stored state.
73
- * Returns which stacks have changed.
74
- */
75
- async compareWithState(currentHashes, appPath) {
76
- const state = await readStateFile(appPath);
77
- const stackChanges = new Map();
78
- let changedCount = 0;
79
- let unchangedCount = 0;
80
- for (const [stackName, currentHash] of currentHashes) {
81
- const storedEntry = state?.templateHashes[stackName];
82
- const hasChanged = !storedEntry || storedEntry.hash !== currentHash;
83
- stackChanges.set(stackName, hasChanged);
84
- if (hasChanged) {
85
- changedCount++;
86
- }
87
- else {
88
- unchangedCount++;
89
- }
90
- }
91
- // Detect stacks that were previously deployed but no longer synthesised (removed stacks)
92
- if (state?.templateHashes) {
93
- for (const stackName of Object.keys(state.templateHashes)) {
94
- if (!currentHashes.has(stackName)) {
95
- stackChanges.set(stackName, true);
96
- changedCount++;
97
- }
98
- }
99
- }
100
- return success({
101
- stackChanges,
102
- currentHashes,
103
- changedCount,
104
- unchangedCount
105
- });
106
- }
107
- /**
108
- * Update state file with new hashes after successful deployment
109
- */
110
- async updateStateAfterDeploy(appPath, deployedStacks, stackStatuses) {
111
- try {
112
- let state = (await readStateFile(appPath)) ?? createEmptyState();
113
- for (const [stackName, hash] of deployedStacks) {
114
- const status = stackStatuses?.get(stackName);
115
- state = updateTemplateHash(state, stackName, hash, status);
116
- }
117
- await writeStateFile(appPath, state);
118
- return success(undefined);
119
- }
120
- catch (error) {
121
- return failure(new TemplateHashError(`Failed to write state file: ${getStateFilePath(appPath)}`, "state_write_failed", { appPath, error }));
122
- }
123
- }
124
- /**
125
- * Get state file path for an application (exposed for testing/logging)
126
- */
127
- getStateFilePath(appPath) {
128
- return getStateFilePath(appPath);
129
- }
130
- /**
131
- * Check if a specific stack has changes
132
- */
133
- stackHasChanges(comparison, stackName) {
134
- return comparison.stackChanges.get(stackName) ?? true;
135
- }
136
- /**
137
- * Get list of stacks that have changes
138
- */
139
- getChangedStacks(comparison) {
140
- return Array.from(comparison.stackChanges.entries())
141
- .filter(([, hasChanges]) => hasChanges)
142
- .map(([stackName]) => stackName);
143
- }
144
- /**
145
- * Get list of stacks that are unchanged
146
- */
147
- getUnchangedStacks(comparison) {
148
- return Array.from(comparison.stackChanges.entries())
149
- .filter(([, hasChanges]) => !hasChanges)
150
- .map(([stackName]) => stackName);
151
- }
152
- }
1
+ import{createHash as y}from"crypto";import{readFile as w,readdir as S}from"fs/promises";import{join as C,basename as H}from"path";import{fileExists as T}from"@fjall/util/fsHelpers";import{success as l,failure as c}from"@fjall/generator";import{BaseServiceError as E}from"../../types/errors/ServiceError.js";import{readStateFile as p,writeStateFile as _,createEmptyState as F,updateTemplateHash as N,getStateFilePath as u}from"../../types/FjallState.js";class f extends E{errorType;constructor(e,t,r,a=!1){super(`TEMPLATE_HASH_${t.toUpperCase()}`,e,r,a),this.errorType=t}}const d=".template.json";class b{async computeTemplateHash(e){try{const t=await w(e,"utf-8"),r=JSON.stringify(JSON.parse(t)),a=y("sha256").update(r).digest("hex");return l(a)}catch(t){return c(new f(`Failed to hash template: ${e}`,"hash_failed",{path:e,error:t}))}}async getTemplateHashes(e){if(!await T(e))return c(new f(`CDK output directory not found: ${e}`,"cdk_out_not_found",{path:e}));try{const r=(await S(e)).filter(s=>s.endsWith(d)),a=new Map;for(const s of r){const n=H(s,d),o=C(e,s),i=await this.computeTemplateHash(o);if(!i.success)return c(i.error);a.set(n,i.data)}return l(a)}catch(t){return c(new f(`Failed to read CDK output directory: ${e}`,"read_failed",{path:e,error:t}))}}async compareWithState(e,t){const r=await p(t),a=new Map;let s=0,n=0;for(const[o,i]of e){const h=r?.templateHashes[o],m=!h||h.hash!==i;a.set(o,m),m?s++:n++}if(r?.templateHashes)for(const o of Object.keys(r.templateHashes))e.has(o)||(a.set(o,!0),s++);return l({stackChanges:a,currentHashes:e,changedCount:s,unchangedCount:n})}async updateStateAfterDeploy(e,t,r){try{let a=await p(e)??F();for(const[s,n]of t){const o=r?.get(s);a=N(a,s,n,o)}return await _(e,a),l(void 0)}catch(a){return c(new f(`Failed to write state file: ${u(e)}`,"state_write_failed",{appPath:e,error:a}))}}getStateFilePath(e){return u(e)}stackHasChanges(e,t){return e.stackChanges.get(t)??!0}getChangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>t).map(([t])=>t)}getUnchangedStacks(e){return Array.from(e.stackChanges.entries()).filter(([,t])=>!t).map(([t])=>t)}}export{f as TemplateHashError,b as TemplateHashService};
@@ -1,505 +1 @@
1
- import { APPLICATION_STACKS, APPLICATION_DEPLOY_ORDER, getApplicationStepName, getApplicationStepId } from "../types/index.js";
2
- import { STEP_IDS, STEP_NAMES, INFRA_STEP_NAME } from "../types/index.js";
3
- /**
4
- * Map from stack type to destroy step ID
5
- */
6
- const DESTROY_STEP_MAP = {
7
- Network: STEP_IDS.NETWORK_DESTROY,
8
- Storage: STEP_IDS.STORAGE_DESTROY,
9
- Messaging: STEP_IDS.MESSAGING_DESTROY,
10
- Database: STEP_IDS.DATABASE_DESTROY,
11
- Compute: STEP_IDS.COMPUTE_DESTROY,
12
- Cdn: STEP_IDS.CDN_DESTROY
13
- };
14
- /**
15
- * Get the destroy step ID for a given stack type
16
- */
17
- export function getDestroyStepId(stackType) {
18
- return DESTROY_STEP_MAP[stackType] ?? `${stackType.toLowerCase()}-destroy`;
19
- }
20
- /**
21
- * Step registry providing step filtering and registry logic.
22
- * Step definitions (STEP_IDS, STEP_NAMES, StepDefinition, StepContext)
23
- * live in types/stepDefinitions.ts — the single source of truth.
24
- *
25
- * This class was moved from CLI to deploy-core so both CLI and webapp
26
- * can use the same step registry.
27
- */
28
- export class StepRegistry {
29
- /**
30
- * Unified deployment steps for ALL deployment types
31
- * Key format: "{deploymentType}-{operation}"
32
- */
33
- static DEPLOYMENT_STEPS = new Map([
34
- [
35
- "application-deploy",
36
- [
37
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
38
- {
39
- id: STEP_IDS.DETECT_CONFIG,
40
- name: STEP_NAMES.PREPARE_DEPLOY
41
- },
42
- {
43
- id: STEP_IDS.BOOTSTRAP,
44
- name: STEP_NAMES.BOOTSTRAP,
45
- conditions: { requiresInfra: true }
46
- },
47
- {
48
- id: getApplicationStepId(APPLICATION_STACKS.NETWORK, "deploy"),
49
- name: getApplicationStepName(APPLICATION_STACKS.NETWORK, "deploy"),
50
- conditions: { requiresInfra: true, requiresNetwork: true }
51
- },
52
- {
53
- id: getApplicationStepId(APPLICATION_STACKS.STORAGE, "deploy"),
54
- name: getApplicationStepName(APPLICATION_STACKS.STORAGE, "deploy"),
55
- conditions: { requiresInfra: true, requiresStorage: true }
56
- },
57
- {
58
- id: getApplicationStepId(APPLICATION_STACKS.MESSAGING, "deploy"),
59
- name: getApplicationStepName(APPLICATION_STACKS.MESSAGING, "deploy"),
60
- conditions: { requiresInfra: true, requiresMessaging: true }
61
- },
62
- {
63
- id: STEP_IDS.DOCKER_OPERATIONS,
64
- name: "Docker operations", // Dynamic name will be set by context
65
- conditions: {
66
- requiresDocker: true,
67
- requiresCompute: true,
68
- skipForInfraOnly: true
69
- }
70
- },
71
- {
72
- id: STEP_IDS.TAG_ECR_IMAGES,
73
- name: "Tagging container images",
74
- conditions: {
75
- requiresImageTagging: true,
76
- requiresCompute: true,
77
- skipForInfraOnly: true
78
- }
79
- },
80
- {
81
- id: getApplicationStepId(APPLICATION_STACKS.DATABASE, "deploy"),
82
- name: getApplicationStepName(APPLICATION_STACKS.DATABASE, "deploy"),
83
- conditions: { requiresInfra: true, requiresDatabase: true }
84
- },
85
- {
86
- id: getApplicationStepId(APPLICATION_STACKS.COMPUTE, "deploy"),
87
- name: getApplicationStepName(APPLICATION_STACKS.COMPUTE, "deploy"),
88
- conditions: { requiresInfra: true, requiresCompute: true }
89
- },
90
- {
91
- id: getApplicationStepId(APPLICATION_STACKS.CDN, "deploy"),
92
- name: getApplicationStepName(APPLICATION_STACKS.CDN, "deploy"),
93
- conditions: { requiresInfra: true, requiresCdn: true }
94
- },
95
- {
96
- id: STEP_IDS.ECS_UPDATE,
97
- name: "Updating ECS service",
98
- conditions: { requiresECS: true, requiresCompute: true }
99
- },
100
- {
101
- id: STEP_IDS.ECS_MONITOR,
102
- name: "Monitoring deployment",
103
- conditions: { requiresECS: true, requiresCompute: true }
104
- }
105
- ]
106
- ],
107
- [
108
- "application-destroy",
109
- [
110
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
111
- { id: STEP_IDS.DETECT_CONFIG, name: STEP_NAMES.CHECK_INFRA_STATE },
112
- {
113
- id: getApplicationStepId(APPLICATION_STACKS.CDN, "destroy"),
114
- name: getApplicationStepName(APPLICATION_STACKS.CDN, "destroy"),
115
- conditions: { requiresCdn: true }
116
- },
117
- {
118
- id: getApplicationStepId(APPLICATION_STACKS.COMPUTE, "destroy"),
119
- name: getApplicationStepName(APPLICATION_STACKS.COMPUTE, "destroy")
120
- },
121
- {
122
- id: getApplicationStepId(APPLICATION_STACKS.DATABASE, "destroy"),
123
- name: getApplicationStepName(APPLICATION_STACKS.DATABASE, "destroy")
124
- },
125
- {
126
- id: getApplicationStepId(APPLICATION_STACKS.MESSAGING, "destroy"),
127
- name: getApplicationStepName(APPLICATION_STACKS.MESSAGING, "destroy"),
128
- conditions: { requiresMessaging: true }
129
- },
130
- {
131
- id: getApplicationStepId(APPLICATION_STACKS.STORAGE, "destroy"),
132
- name: getApplicationStepName(APPLICATION_STACKS.STORAGE, "destroy"),
133
- conditions: { requiresStorage: true }
134
- },
135
- {
136
- id: getApplicationStepId(APPLICATION_STACKS.NETWORK, "destroy"),
137
- name: getApplicationStepName(APPLICATION_STACKS.NETWORK, "destroy")
138
- }
139
- ]
140
- ],
141
- [
142
- "organisation-deploy",
143
- [
144
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
145
- { id: STEP_IDS.ORG_SETUP, name: "Configuring organisation" },
146
- { id: STEP_IDS.PREPARE, name: STEP_NAMES.PREPARE_DEPLOY },
147
- { id: STEP_IDS.BOOTSTRAP, name: STEP_NAMES.BOOTSTRAP },
148
- {
149
- id: STEP_IDS.DEPLOY,
150
- name: "Deploying organisation",
151
- conditions: { requiresOrgChanges: true }
152
- },
153
- {
154
- id: STEP_IDS.CASCADE_PLATFORM,
155
- name: "Deploying platform",
156
- conditions: {
157
- requiresPlatformAccount: true,
158
- requiresPlatformChanges: true
159
- }
160
- },
161
- {
162
- id: STEP_IDS.CASCADE_DOMAINS,
163
- name: "Deploying domains",
164
- conditions: {
165
- requiresDomainConfiguration: true,
166
- requiresDomainChanges: true
167
- }
168
- },
169
- {
170
- id: STEP_IDS.CASCADE_ACCOUNTS,
171
- name: "Deploying accounts",
172
- conditions: {
173
- requiresMemberAccounts: true,
174
- requiresAccountChanges: true
175
- }
176
- }
177
- ]
178
- ],
179
- [
180
- "organisation-destroy",
181
- [
182
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
183
- { id: STEP_IDS.CFN_CHECK, name: STEP_NAMES.CHECK_INFRA_STATE },
184
- {
185
- id: STEP_IDS.CASCADE_ACCOUNTS,
186
- name: "Destroying account infrastructure",
187
- conditions: { requiresMemberAccounts: true }
188
- },
189
- {
190
- id: STEP_IDS.CASCADE_PLATFORM,
191
- name: "Destroying platform infrastructure",
192
- conditions: { requiresPlatformAccount: true }
193
- },
194
- { id: STEP_IDS.DESTROY, name: "Destroying organisation infrastructure" }
195
- ]
196
- ],
197
- [
198
- "platform-deploy",
199
- [
200
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
201
- { id: STEP_IDS.CONNECT, name: INFRA_STEP_NAME.CONNECT },
202
- { id: STEP_IDS.PREPARE_ENVIRONMENT, name: INFRA_STEP_NAME.PREPARE },
203
- { id: STEP_IDS.DEPLOY, name: INFRA_STEP_NAME.DEPLOY },
204
- { id: STEP_IDS.MONITORING, name: INFRA_STEP_NAME.MONITORING }
205
- ]
206
- ],
207
- [
208
- "platform-destroy",
209
- [
210
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
211
- { id: STEP_IDS.CFN_CHECK, name: "Checking infrastructure state" },
212
- { id: STEP_IDS.ORG_DESTROY, name: "Destroying platform infrastructure" }
213
- ]
214
- ],
215
- [
216
- "account-deploy",
217
- [
218
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
219
- { id: STEP_IDS.CONNECT, name: INFRA_STEP_NAME.CONNECT },
220
- { id: STEP_IDS.PREPARE_ENVIRONMENT, name: INFRA_STEP_NAME.PREPARE },
221
- { id: STEP_IDS.DEPLOY, name: INFRA_STEP_NAME.DEPLOY },
222
- { id: STEP_IDS.MONITORING, name: INFRA_STEP_NAME.MONITORING }
223
- ]
224
- ],
225
- [
226
- "account-destroy",
227
- [
228
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
229
- { id: STEP_IDS.CFN_CHECK, name: "Checking infrastructure state" },
230
- { id: STEP_IDS.ORG_DESTROY, name: "Destroying account infrastructure" }
231
- ]
232
- ]
233
- ]);
234
- /**
235
- * Get filtered steps based on deployment context
236
- * This is the SINGLE method for getting steps for ANY deployment
237
- */
238
- static getSteps(context) {
239
- // Special handling for deploy-only mode
240
- if (context.deploymentType === "application" &&
241
- context.operation === "deploy" &&
242
- context.deployOnly) {
243
- // Return simplified steps for deploy-only mode.
244
- // Stack step IDs are derived from APPLICATION_DEPLOY_ORDER so they
245
- // match the IDs deploy-core actually emits during deployment.
246
- return [
247
- { id: STEP_IDS.AUTH, name: STEP_NAMES.AUTH },
248
- {
249
- id: STEP_IDS.DOCKER_OPERATIONS,
250
- name: "Build and push Docker container"
251
- },
252
- ...APPLICATION_DEPLOY_ORDER.map((stack) => ({
253
- id: getApplicationStepId(stack, "deploy"),
254
- name: getApplicationStepName(stack, "deploy")
255
- }))
256
- ];
257
- }
258
- const key = `${context.deploymentType}-${context.operation}`;
259
- const steps = this.DEPLOYMENT_STEPS.get(key) ?? [];
260
- // Apply context-based filtering
261
- const filteredSteps = steps
262
- .filter((step) => {
263
- // Application-specific filtering
264
- if (context.deploymentType === "application") {
265
- const isOpenNextDeploy = context.builderName === "opennext";
266
- // Skip infrastructure steps in deploy-only mode
267
- if (step.conditions?.requiresInfra && context.deployOnly) {
268
- return false;
269
- }
270
- // Resource gating: when resources are known, skip steps for absent resources
271
- if (context.resources) {
272
- const r = context.resources;
273
- if (step.conditions?.requiresNetwork && !r.hasNetwork)
274
- return false;
275
- if (step.conditions?.requiresCompute && !r.hasCompute)
276
- return false;
277
- if (step.conditions?.requiresDatabase && !r.hasDatabase)
278
- return false;
279
- if (step.conditions?.requiresStorage && !r.hasStorage)
280
- return false;
281
- if (step.conditions?.requiresMessaging && !r.hasMessaging)
282
- return false;
283
- if (step.conditions?.requiresCdn && !r.hasCdn)
284
- return false;
285
- }
286
- else if (context.operation === "deploy" && !isOpenNextDeploy) {
287
- // Pre-synth (standard deploy): hide ALL resource-specific steps
288
- // until manifest detection completes post-synth.
289
- // OpenNext patterns always use all 6 stacks so they keep theirs.
290
- // Destroy never runs synth so resources stays undefined — show all steps.
291
- const isResourceStep = step.conditions?.requiresNetwork ||
292
- step.conditions?.requiresCompute ||
293
- step.conditions?.requiresDatabase ||
294
- step.conditions?.requiresStorage ||
295
- step.conditions?.requiresMessaging ||
296
- step.conditions?.requiresCdn;
297
- if (isResourceStep) {
298
- return false;
299
- }
300
- }
301
- // Handle Docker operations step - skip for OpenNext deployments
302
- if (step.conditions?.requiresDocker ||
303
- step.id === STEP_IDS.DOCKER_OPERATIONS) {
304
- // Skip Docker steps for OpenNext deployments (uses Lambda instead)
305
- if (isOpenNextDeploy) {
306
- return false;
307
- }
308
- // Skip in infra-only mode
309
- if (context.infraOnly) {
310
- return false;
311
- }
312
- // Skip when post-synth detection confirms no Compute stack
313
- if (context.resources && !context.resources.hasCompute) {
314
- return false;
315
- }
316
- // For deploy-only mode, always show if we have a Dockerfile
317
- if (context.deployOnly) {
318
- return context.hasDockerfile === true;
319
- }
320
- // For normal mode, show if:
321
- // - We have a Dockerfile (build+push), OR
322
- // - No Dockerfile + Compute stack (ECR init + welcome image)
323
- if (context.hasDockerfile === true) {
324
- return true;
325
- }
326
- if (context.hasDockerfile === false && !context.isManagedAccount) {
327
- return true;
328
- }
329
- // Don't show during initial state (hasDockerfile undefined)
330
- return false;
331
- }
332
- // Handle ECS update/monitor steps - skip for OpenNext, only show in deploy-only mode
333
- if (step.conditions?.requiresECS) {
334
- if (isOpenNextDeploy) {
335
- return false;
336
- }
337
- return (context.deployOnly === true && context.hasDockerfile === true);
338
- }
339
- // Handle image tagging step - show when no Dockerfile + Compute exists
340
- // ECS expects :<serviceName>-latest tags; initialiseECR only pushes :latest
341
- if (step.conditions?.requiresImageTagging) {
342
- if (context.infraOnly) {
343
- return false;
344
- }
345
- if (isOpenNextDeploy) {
346
- return false;
347
- }
348
- // Skip when post-synth detection confirms no Compute stack
349
- if (context.resources && !context.resources.hasCompute) {
350
- return false;
351
- }
352
- // Show when no Dockerfile (we're not building our own image)
353
- return context.hasDockerfile === false;
354
- }
355
- }
356
- // Organisation cascade filtering: hide cascade steps when no accounts exist
357
- if (step.conditions?.requiresMemberAccounts &&
358
- !context.hasMemberAccounts) {
359
- return false;
360
- }
361
- if (step.conditions?.requiresPlatformAccount &&
362
- !context.hasPlatformAccount) {
363
- return false;
364
- }
365
- // Organisation change-detection filtering: hide steps until detection confirms changes.
366
- // Steps only appear when the corresponding flag is explicitly true.
367
- // Pre-detection (undefined) hides steps to avoid flash-then-remove.
368
- if (step.conditions?.requiresOrgChanges &&
369
- context.hasOrgChanges !== true) {
370
- return false;
371
- }
372
- if (step.conditions?.requiresPlatformChanges &&
373
- context.hasPlatformChanges !== true) {
374
- return false;
375
- }
376
- if (step.conditions?.requiresAccountChanges &&
377
- context.hasAccountChanges !== true) {
378
- return false;
379
- }
380
- if (step.conditions?.requiresDomainConfiguration &&
381
- !context.hasDomainConfiguration) {
382
- return false;
383
- }
384
- if (step.conditions?.requiresDomainChanges &&
385
- context.hasDomainChanges !== true) {
386
- return false;
387
- }
388
- return true;
389
- })
390
- .map((step) => {
391
- // Dynamically set the name for docker-operations based on context
392
- if (step.id === STEP_IDS.DOCKER_OPERATIONS) {
393
- return {
394
- ...step,
395
- name: context.hasDockerfile
396
- ? "Building and pushing Docker image"
397
- : "Initialising container repository"
398
- };
399
- }
400
- return step;
401
- });
402
- return filteredSteps;
403
- }
404
- /**
405
- * Get step names for UI display
406
- */
407
- static getStepNames(context) {
408
- return this.getSteps(context).map((s) => s.name);
409
- }
410
- /**
411
- * Get the index of a specific step in the filtered list
412
- */
413
- static getStepIndex(stepId, context) {
414
- const steps = this.getSteps(context);
415
- return steps.findIndex((s) => s.id === stepId);
416
- }
417
- /**
418
- * Get a step by ID from all steps
419
- */
420
- static getStepById(stepId, deploymentType = "application") {
421
- // Search in deploy steps first
422
- const deployKey = `${deploymentType}-deploy`;
423
- const deploySteps = this.DEPLOYMENT_STEPS.get(deployKey) || [];
424
- const found = deploySteps.find((s) => s.id === stepId);
425
- if (found)
426
- return found;
427
- // Then search in destroy steps
428
- const destroyKey = `${deploymentType}-destroy`;
429
- const destroySteps = this.DEPLOYMENT_STEPS.get(destroyKey) || [];
430
- return destroySteps.find((s) => s.id === stepId);
431
- }
432
- /**
433
- * Check if a step should be included based on context
434
- */
435
- static isStepIncluded(stepId, context) {
436
- const steps = this.getSteps(context);
437
- return steps.some((s) => s.id === stepId);
438
- }
439
- /**
440
- * Convenience method for creating context
441
- */
442
- static createContext(deploymentType, operation, options) {
443
- return {
444
- deploymentType,
445
- operation,
446
- deployOnly: options?.deployOnly ?? false,
447
- infraOnly: options?.infraOnly ?? false,
448
- isManagedAccount: options?.isManagedAccount ?? false,
449
- hasDockerfile: options?.hasDockerfile ?? false,
450
- pattern: options?.pattern ?? null,
451
- builderName: options?.builderName,
452
- resources: options?.resources
453
- };
454
- }
455
- /**
456
- * Get all deployment types
457
- */
458
- static getDeploymentTypes() {
459
- return ["application", "organisation", "platform", "account"];
460
- }
461
- /**
462
- * Get stack names for a deployment type
463
- */
464
- static getStackNames(deploymentType) {
465
- switch (deploymentType) {
466
- case "application":
467
- return [...APPLICATION_DEPLOY_ORDER];
468
- case "organisation":
469
- return ["Organisation"];
470
- case "platform":
471
- return ["Platform"];
472
- case "account":
473
- return ["Account"];
474
- default:
475
- return [];
476
- }
477
- }
478
- /**
479
- * Get infrastructure step IDs for skipping
480
- */
481
- static getInfraStepIds() {
482
- return [
483
- STEP_IDS.CFN_CHECK,
484
- STEP_IDS.BOOTSTRAP,
485
- STEP_IDS.DIFF,
486
- STEP_IDS.NETWORK,
487
- STEP_IDS.STORAGE,
488
- STEP_IDS.MESSAGING,
489
- STEP_IDS.DATABASE,
490
- STEP_IDS.COMPUTE,
491
- STEP_IDS.CDN
492
- ];
493
- }
494
- /**
495
- * Get Docker step IDs for skipping
496
- */
497
- static getDockerStepIds() {
498
- return [
499
- STEP_IDS.ECR_INIT,
500
- STEP_IDS.DOCKER_DEPLOY,
501
- STEP_IDS.ECS_UPDATE,
502
- STEP_IDS.ECS_MONITOR
503
- ];
504
- }
505
- }
1
+ import{APPLICATION_STACKS as n,APPLICATION_DEPLOY_ORDER as m,getApplicationStepName as t,getApplicationStepId as a}from"../types/index.js";import{STEP_IDS as e,STEP_NAMES as s,INFRA_STEP_NAME as c}from"../types/index.js";const A={Network:e.NETWORK_DESTROY,Storage:e.STORAGE_DESTROY,Messaging:e.MESSAGING_DESTROY,Database:e.DATABASE_DESTROY,Compute:e.COMPUTE_DESTROY,Cdn:e.CDN_DESTROY};function S(f){return A[f]??`${f.toLowerCase()}-destroy`}class g{static DEPLOYMENT_STEPS=new Map([["application-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.PREPARE_DEPLOY},{id:e.BOOTSTRAP,name:s.BOOTSTRAP,conditions:{requiresInfra:!0}},{id:a(n.NETWORK,"deploy"),name:t(n.NETWORK,"deploy"),conditions:{requiresInfra:!0,requiresNetwork:!0}},{id:a(n.STORAGE,"deploy"),name:t(n.STORAGE,"deploy"),conditions:{requiresInfra:!0,requiresStorage:!0}},{id:a(n.MESSAGING,"deploy"),name:t(n.MESSAGING,"deploy"),conditions:{requiresInfra:!0,requiresMessaging:!0}},{id:e.DOCKER_OPERATIONS,name:"Docker operations",conditions:{requiresDocker:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:e.TAG_ECR_IMAGES,name:"Tagging container images",conditions:{requiresImageTagging:!0,requiresCompute:!0,skipForInfraOnly:!0}},{id:a(n.DATABASE,"deploy"),name:t(n.DATABASE,"deploy"),conditions:{requiresInfra:!0,requiresDatabase:!0}},{id:a(n.COMPUTE,"deploy"),name:t(n.COMPUTE,"deploy"),conditions:{requiresInfra:!0,requiresCompute:!0}},{id:a(n.CDN,"deploy"),name:t(n.CDN,"deploy"),conditions:{requiresInfra:!0,requiresCdn:!0}},{id:e.ECS_UPDATE,name:"Updating ECS service",conditions:{requiresECS:!0,requiresCompute:!0}},{id:e.ECS_MONITOR,name:"Monitoring deployment",conditions:{requiresECS:!0,requiresCompute:!0}}]],["application-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.DETECT_CONFIG,name:s.CHECK_INFRA_STATE},{id:a(n.CDN,"destroy"),name:t(n.CDN,"destroy"),conditions:{requiresCdn:!0}},{id:a(n.COMPUTE,"destroy"),name:t(n.COMPUTE,"destroy")},{id:a(n.DATABASE,"destroy"),name:t(n.DATABASE,"destroy")},{id:a(n.MESSAGING,"destroy"),name:t(n.MESSAGING,"destroy"),conditions:{requiresMessaging:!0}},{id:a(n.STORAGE,"destroy"),name:t(n.STORAGE,"destroy"),conditions:{requiresStorage:!0}},{id:a(n.NETWORK,"destroy"),name:t(n.NETWORK,"destroy")}]],["organisation-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.ORG_SETUP,name:"Configuring organisation"},{id:e.PREPARE,name:s.PREPARE_DEPLOY},{id:e.BOOTSTRAP,name:s.BOOTSTRAP},{id:e.DEPLOY,name:"Deploying organisation",conditions:{requiresOrgChanges:!0}},{id:e.CASCADE_PLATFORM,name:"Deploying platform",conditions:{requiresPlatformAccount:!0,requiresPlatformChanges:!0}},{id:e.CASCADE_DOMAINS,name:"Deploying domains",conditions:{requiresDomainConfiguration:!0,requiresDomainChanges:!0}},{id:e.CASCADE_ACCOUNTS,name:"Deploying accounts",conditions:{requiresMemberAccounts:!0,requiresAccountChanges:!0}}]],["organisation-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:s.CHECK_INFRA_STATE},{id:e.CASCADE_ACCOUNTS,name:"Destroying account infrastructure",conditions:{requiresMemberAccounts:!0}},{id:e.CASCADE_PLATFORM,name:"Destroying platform infrastructure",conditions:{requiresPlatformAccount:!0}},{id:e.DESTROY,name:"Destroying organisation infrastructure"}]],["platform-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:c.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:c.PREPARE},{id:e.DEPLOY,name:c.DEPLOY},{id:e.MONITORING,name:c.MONITORING}]],["platform-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying platform infrastructure"}]],["account-deploy",[{id:e.AUTH,name:s.AUTH},{id:e.CONNECT,name:c.CONNECT},{id:e.PREPARE_ENVIRONMENT,name:c.PREPARE},{id:e.DEPLOY,name:c.DEPLOY},{id:e.MONITORING,name:c.MONITORING}]],["account-destroy",[{id:e.AUTH,name:s.AUTH},{id:e.CFN_CHECK,name:"Checking infrastructure state"},{id:e.ORG_DESTROY,name:"Destroying account infrastructure"}]]]);static getSteps(r){if(r.deploymentType==="application"&&r.operation==="deploy"&&r.deployOnly)return[{id:e.AUTH,name:s.AUTH},{id:e.DOCKER_OPERATIONS,name:"Build and push Docker container"},...m.map(i=>({id:a(i,"deploy"),name:t(i,"deploy")}))];const o=`${r.deploymentType}-${r.operation}`;return(this.DEPLOYMENT_STEPS.get(o)??[]).filter(i=>{if(r.deploymentType==="application"){const E=r.builderName==="opennext";if(i.conditions?.requiresInfra&&r.deployOnly)return!1;if(r.resources){const d=r.resources;if(i.conditions?.requiresNetwork&&!d.hasNetwork||i.conditions?.requiresCompute&&!d.hasCompute||i.conditions?.requiresDatabase&&!d.hasDatabase||i.conditions?.requiresStorage&&!d.hasStorage||i.conditions?.requiresMessaging&&!d.hasMessaging||i.conditions?.requiresCdn&&!d.hasCdn)return!1}else if(r.operation==="deploy"&&!E&&(i.conditions?.requiresNetwork||i.conditions?.requiresCompute||i.conditions?.requiresDatabase||i.conditions?.requiresStorage||i.conditions?.requiresMessaging||i.conditions?.requiresCdn))return!1;if(i.conditions?.requiresDocker||i.id===e.DOCKER_OPERATIONS)return E||r.infraOnly||r.resources&&!r.resources.hasCompute?!1:r.deployOnly?r.hasDockerfile===!0:r.hasDockerfile===!0||r.hasDockerfile===!1&&!r.isManagedAccount;if(i.conditions?.requiresECS)return E?!1:r.deployOnly===!0&&r.hasDockerfile===!0;if(i.conditions?.requiresImageTagging)return r.infraOnly||E||r.resources&&!r.resources.hasCompute?!1:r.hasDockerfile===!1}return!(i.conditions?.requiresMemberAccounts&&!r.hasMemberAccounts||i.conditions?.requiresPlatformAccount&&!r.hasPlatformAccount||i.conditions?.requiresOrgChanges&&r.hasOrgChanges!==!0||i.conditions?.requiresPlatformChanges&&r.hasPlatformChanges!==!0||i.conditions?.requiresAccountChanges&&r.hasAccountChanges!==!0||i.conditions?.requiresDomainConfiguration&&!r.hasDomainConfiguration||i.conditions?.requiresDomainChanges&&r.hasDomainChanges!==!0)}).map(i=>i.id===e.DOCKER_OPERATIONS?{...i,name:r.hasDockerfile?"Building and pushing Docker image":"Initialising container repository"}:i)}static getStepNames(r){return this.getSteps(r).map(o=>o.name)}static getStepIndex(r,o){return this.getSteps(o).findIndex(l=>l.id===r)}static getStepById(r,o="application"){const u=`${o}-deploy`,i=(this.DEPLOYMENT_STEPS.get(u)||[]).find(T=>T.id===r);if(i)return i;const E=`${o}-destroy`;return(this.DEPLOYMENT_STEPS.get(E)||[]).find(T=>T.id===r)}static isStepIncluded(r,o){return this.getSteps(o).some(l=>l.id===r)}static createContext(r,o,u){return{deploymentType:r,operation:o,deployOnly:u?.deployOnly??!1,infraOnly:u?.infraOnly??!1,isManagedAccount:u?.isManagedAccount??!1,hasDockerfile:u?.hasDockerfile??!1,pattern:u?.pattern??null,builderName:u?.builderName,resources:u?.resources}}static getDeploymentTypes(){return["application","organisation","platform","account"]}static getStackNames(r){switch(r){case"application":return[...m];case"organisation":return["Organisation"];case"platform":return["Platform"];case"account":return["Account"];default:return[]}}static getInfraStepIds(){return[e.CFN_CHECK,e.BOOTSTRAP,e.DIFF,e.NETWORK,e.STORAGE,e.MESSAGING,e.DATABASE,e.COMPUTE,e.CDN]}static getDockerStepIds(){return[e.ECR_INIT,e.DOCKER_DEPLOY,e.ECS_UPDATE,e.ECS_MONITOR]}}export{g as StepRegistry,S as getDestroyStepId};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};