@fjall/deploy-core 0.89.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/aws/AwsProvider.d.ts +39 -0
  3. package/dist/src/aws/AwsProvider.js +1 -0
  4. package/dist/src/aws/SimpleAwsProvider.d.ts +22 -0
  5. package/dist/src/aws/SimpleAwsProvider.js +73 -0
  6. package/dist/src/aws/index.d.ts +4 -0
  7. package/dist/src/aws/index.js +3 -0
  8. package/dist/src/aws/organisations/accounts.d.ts +21 -0
  9. package/dist/src/aws/organisations/accounts.js +99 -0
  10. package/dist/src/aws/organisations/backup.d.ts +12 -0
  11. package/dist/src/aws/organisations/backup.js +28 -0
  12. package/dist/src/aws/organisations/costAllocation.d.ts +12 -0
  13. package/dist/src/aws/organisations/costAllocation.js +26 -0
  14. package/dist/src/aws/organisations/identityCentre.d.ts +8 -0
  15. package/dist/src/aws/organisations/identityCentre.js +19 -0
  16. package/dist/src/aws/organisations/index.d.ts +16 -0
  17. package/dist/src/aws/organisations/index.js +12 -0
  18. package/dist/src/aws/organisations/ipam.d.ts +7 -0
  19. package/dist/src/aws/organisations/ipam.js +18 -0
  20. package/dist/src/aws/organisations/organisation.d.ts +12 -0
  21. package/dist/src/aws/organisations/organisation.js +94 -0
  22. package/dist/src/aws/organisations/organisationalUnits.d.ts +19 -0
  23. package/dist/src/aws/organisations/organisationalUnits.js +125 -0
  24. package/dist/src/aws/organisations/policies.d.ts +7 -0
  25. package/dist/src/aws/organisations/policies.js +36 -0
  26. package/dist/src/aws/organisations/ram.d.ts +7 -0
  27. package/dist/src/aws/organisations/ram.js +15 -0
  28. package/dist/src/aws/organisations/serviceAccess.d.ts +7 -0
  29. package/dist/src/aws/organisations/serviceAccess.js +38 -0
  30. package/dist/src/aws/organisations/trustedAccess.d.ts +7 -0
  31. package/dist/src/aws/organisations/trustedAccess.js +15 -0
  32. package/dist/src/aws/organisations/types.d.ts +29 -0
  33. package/dist/src/aws/organisations/types.js +16 -0
  34. package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +32 -0
  35. package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +228 -0
  36. package/dist/src/aws/utils/cloudformationEvents.d.ts +98 -0
  37. package/dist/src/aws/utils/cloudformationEvents.js +596 -0
  38. package/dist/src/aws/utils/errors.d.ts +26 -0
  39. package/dist/src/aws/utils/errors.js +59 -0
  40. package/dist/src/aws/utils/regions.d.ts +1 -0
  41. package/dist/src/aws/utils/regions.js +1 -0
  42. package/dist/src/aws/utils/stackStatus.d.ts +23 -0
  43. package/dist/src/aws/utils/stackStatus.js +90 -0
  44. package/dist/src/index.d.ts +35 -0
  45. package/dist/src/index.js +45 -0
  46. package/dist/src/orchestration/applicationDeploy.d.ts +11 -0
  47. package/dist/src/orchestration/applicationDeploy.js +327 -0
  48. package/dist/src/orchestration/contextHelpers.d.ts +9 -0
  49. package/dist/src/orchestration/contextHelpers.js +14 -0
  50. package/dist/src/orchestration/deploy.d.ts +10 -0
  51. package/dist/src/orchestration/deploy.js +42 -0
  52. package/dist/src/orchestration/detectionPipeline.d.ts +23 -0
  53. package/dist/src/orchestration/detectionPipeline.js +65 -0
  54. package/dist/src/orchestration/dockerInterface.d.ts +56 -0
  55. package/dist/src/orchestration/dockerInterface.js +1 -0
  56. package/dist/src/orchestration/domainInterface.d.ts +37 -0
  57. package/dist/src/orchestration/domainInterface.js +1 -0
  58. package/dist/src/orchestration/index.d.ts +8 -0
  59. package/dist/src/orchestration/index.js +3 -0
  60. package/dist/src/orchestration/organisationDeploy.d.ts +16 -0
  61. package/dist/src/orchestration/organisationDeploy.js +382 -0
  62. package/dist/src/orchestration/organisationSetup.d.ts +42 -0
  63. package/dist/src/orchestration/organisationSetup.js +227 -0
  64. package/dist/src/orchestration/resolveOperation.d.ts +10 -0
  65. package/dist/src/orchestration/resolveOperation.js +53 -0
  66. package/dist/src/orchestration/serviceFactory.d.ts +15 -0
  67. package/dist/src/orchestration/serviceFactory.js +16 -0
  68. package/dist/src/services/application/ApplicationStackService.d.ts +93 -0
  69. package/dist/src/services/application/ApplicationStackService.js +436 -0
  70. package/dist/src/services/application/index.d.ts +1 -0
  71. package/dist/src/services/application/index.js +1 -0
  72. package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +12 -0
  73. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +67 -0
  74. package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +30 -0
  75. package/dist/src/services/infrastructure/CdkCommandRunner.js +241 -0
  76. package/dist/src/services/infrastructure/CdkErrorFormatter.d.ts +4 -0
  77. package/dist/src/services/infrastructure/CdkErrorFormatter.js +194 -0
  78. package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +19 -0
  79. package/dist/src/services/infrastructure/CdkEventMonitoring.js +41 -0
  80. package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +43 -0
  81. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +125 -0
  82. package/dist/src/services/infrastructure/CdkOutputParser.d.ts +8 -0
  83. package/dist/src/services/infrastructure/CdkOutputParser.js +33 -0
  84. package/dist/src/services/infrastructure/CdkProcessManager.d.ts +20 -0
  85. package/dist/src/services/infrastructure/CdkProcessManager.js +244 -0
  86. package/dist/src/services/infrastructure/CdkService.d.ts +71 -0
  87. package/dist/src/services/infrastructure/CdkService.js +254 -0
  88. package/dist/src/services/infrastructure/CloudFormationService.d.ts +79 -0
  89. package/dist/src/services/infrastructure/CloudFormationService.js +249 -0
  90. package/dist/src/services/infrastructure/index.d.ts +8 -0
  91. package/dist/src/services/infrastructure/index.js +7 -0
  92. package/dist/src/services/supporting/CdkContextBuilder.d.ts +49 -0
  93. package/dist/src/services/supporting/CdkContextBuilder.js +44 -0
  94. package/dist/src/services/supporting/TemplateHashService.d.ts +67 -0
  95. package/dist/src/services/supporting/TemplateHashService.js +152 -0
  96. package/dist/src/services/supporting/helpers.d.ts +46 -0
  97. package/dist/src/services/supporting/helpers.js +81 -0
  98. package/dist/src/services/supporting/index.d.ts +3 -0
  99. package/dist/src/services/supporting/index.js +3 -0
  100. package/dist/src/types/FjallState.d.ts +50 -0
  101. package/dist/src/types/FjallState.js +118 -0
  102. package/dist/src/types/ProgressEvent.d.ts +35 -0
  103. package/dist/src/types/ProgressEvent.js +48 -0
  104. package/dist/src/types/apiClient.d.ts +34 -0
  105. package/dist/src/types/apiClient.js +1 -0
  106. package/dist/src/types/application/ApplicationServiceTypes.d.ts +56 -0
  107. package/dist/src/types/application/ApplicationServiceTypes.js +30 -0
  108. package/dist/src/types/application/index.d.ts +1 -0
  109. package/dist/src/types/application/index.js +1 -0
  110. package/dist/src/types/callbacks.d.ts +36 -0
  111. package/dist/src/types/callbacks.js +1 -0
  112. package/dist/src/types/constants.d.ts +6 -0
  113. package/dist/src/types/constants.js +6 -0
  114. package/dist/src/types/credentials.d.ts +30 -0
  115. package/dist/src/types/credentials.js +1 -0
  116. package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +23 -0
  117. package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -0
  118. package/dist/src/types/deployment/DeploymentTypes.d.ts +29 -0
  119. package/dist/src/types/deployment/DeploymentTypes.js +1 -0
  120. package/dist/src/types/deployment/cloudformation.d.ts +14 -0
  121. package/dist/src/types/deployment/cloudformation.js +1 -0
  122. package/dist/src/types/deployment/index.d.ts +5 -0
  123. package/dist/src/types/deployment/index.js +1 -0
  124. package/dist/src/types/deployment/parallel.d.ts +46 -0
  125. package/dist/src/types/deployment/parallel.js +10 -0
  126. package/dist/src/types/errors/CdkError.d.ts +14 -0
  127. package/dist/src/types/errors/CdkError.js +20 -0
  128. package/dist/src/types/errors/ServiceError.d.ts +86 -0
  129. package/dist/src/types/errors/ServiceError.js +119 -0
  130. package/dist/src/types/events.d.ts +40 -0
  131. package/dist/src/types/events.js +5 -0
  132. package/dist/src/types/index.d.ts +20 -0
  133. package/dist/src/types/index.js +9 -0
  134. package/dist/src/types/operations.d.ts +193 -0
  135. package/dist/src/types/operations.js +285 -0
  136. package/dist/src/types/orgConfig.d.ts +28 -0
  137. package/dist/src/types/orgConfig.js +11 -0
  138. package/dist/src/types/params.d.ts +74 -0
  139. package/dist/src/types/params.js +1 -0
  140. package/dist/src/types/patternDetection.d.ts +43 -0
  141. package/dist/src/types/patternDetection.js +92 -0
  142. package/dist/src/types/validation.d.ts +12 -0
  143. package/dist/src/types/validation.js +1 -0
  144. package/dist/src/util/fsHelpers.d.ts +4 -0
  145. package/dist/src/util/fsHelpers.js +16 -0
  146. package/dist/src/util/index.d.ts +3 -0
  147. package/dist/src/util/index.js +3 -0
  148. package/dist/src/util/securityHelpers.d.ts +31 -0
  149. package/dist/src/util/securityHelpers.js +124 -0
  150. package/dist/src/util/singleton.d.ts +2 -0
  151. package/dist/src/util/singleton.js +9 -0
  152. package/dist/src/util/sleep.d.ts +4 -0
  153. package/dist/src/util/sleep.js +4 -0
  154. package/package.json +42 -0
@@ -0,0 +1,227 @@
1
+ import { success, failure } from "@fjall/generator";
2
+ import { OrganizationsClient } from "@aws-sdk/client-organizations";
3
+ import { RAMClient } from "@aws-sdk/client-ram";
4
+ import { CloudFormationClient } from "@aws-sdk/client-cloudformation";
5
+ import { EC2Client } from "@aws-sdk/client-ec2";
6
+ import { BackupClient } from "@aws-sdk/client-backup";
7
+ import { CostExplorerClient } from "@aws-sdk/client-cost-explorer";
8
+ import { SSOAdminClient } from "@aws-sdk/client-sso-admin";
9
+ import { ensureOrganisationExists } from "../aws/organisations/organisation.js";
10
+ import { enablePolicyTypes } from "../aws/organisations/policies.js";
11
+ import { enableServiceAccess } from "../aws/organisations/serviceAccess.js";
12
+ import { enableRamSharing } from "../aws/organisations/ram.js";
13
+ import { activateTrustedAccess } from "../aws/organisations/trustedAccess.js";
14
+ import { enableIpamDelegatedAdmin } from "../aws/organisations/ipam.js";
15
+ import { updateBackupGlobalSettings } from "../aws/organisations/backup.js";
16
+ import { listAccounts, createAccount } from "../aws/organisations/accounts.js";
17
+ import { ensureOrganisationalUnitsExist, placeAccountsInOUs } from "../aws/organisations/organisationalUnits.js";
18
+ import { activateCostAllocationTags } from "../aws/organisations/costAllocation.js";
19
+ import { checkIdentityCentreStatus } from "../aws/organisations/identityCentre.js";
20
+ /**
21
+ * Orchestrate the full AWS Organisation setup sequence.
22
+ *
23
+ * Runs 12 phases sequentially. Non-fatal phase failures are recorded
24
+ * and execution continues. The only fatal failure is phase 1
25
+ * (create-organisation) since all subsequent phases depend on the org ID.
26
+ */
27
+ export async function runOrganisationSetup(awsProvider, config, callbacks) {
28
+ const phasesCompleted = [];
29
+ const phasesSkipped = [];
30
+ const errors = [];
31
+ const createdAccounts = [];
32
+ let identityCentreStatus;
33
+ const orgsClient = awsProvider.getClient(OrganizationsClient);
34
+ const ramClient = awsProvider.getClient(RAMClient);
35
+ const cfnClient = awsProvider.getClient(CloudFormationClient);
36
+ const ec2Client = awsProvider.getClient(EC2Client);
37
+ const backupClient = awsProvider.getClient(BackupClient);
38
+ const ceClient = awsProvider.getClient(CostExplorerClient);
39
+ const ssoClient = awsProvider.getClient(SSOAdminClient);
40
+ // Phase 1: Ensure organisation exists (fatal if fails)
41
+ callbacks?.onPhaseStart?.("create-organisation");
42
+ callbacks?.onProgress?.("Ensuring AWS Organisation exists");
43
+ const orgResult = await ensureOrganisationExists(orgsClient);
44
+ if (!orgResult.success) {
45
+ callbacks?.onError?.("create-organisation", orgResult.error);
46
+ callbacks?.onPhaseComplete?.("create-organisation", "error");
47
+ return failure(orgResult.error);
48
+ }
49
+ const { orgId, rootId } = orgResult.data;
50
+ callbacks?.onPhaseComplete?.("create-organisation", "completed");
51
+ phasesCompleted.push("create-organisation");
52
+ // Phase 2: Enable policy types
53
+ await executePhase("enable-policies", () => {
54
+ callbacks?.onProgress?.("Enabling organisation policy types");
55
+ return enablePolicyTypes(orgsClient, rootId);
56
+ }, phasesCompleted, errors, callbacks);
57
+ // Phase 3: Enable service access
58
+ await executePhase("enable-service-access", () => {
59
+ callbacks?.onProgress?.("Enabling AWS service access");
60
+ return enableServiceAccess(orgsClient);
61
+ }, phasesCompleted, errors, callbacks);
62
+ // Phase 4: Enable RAM sharing
63
+ await executePhase("enable-ram-sharing", () => {
64
+ callbacks?.onProgress?.("Enabling RAM sharing");
65
+ return enableRamSharing(ramClient);
66
+ }, phasesCompleted, errors, callbacks);
67
+ // Phase 5: Activate trusted access
68
+ await executePhase("activate-trusted-access", () => {
69
+ callbacks?.onProgress?.("Activating CloudFormation trusted access");
70
+ return activateTrustedAccess(cfnClient);
71
+ }, phasesCompleted, errors, callbacks);
72
+ // Phase 6: Enable IPAM delegated admin
73
+ await executePhase("enable-ipam", () => {
74
+ callbacks?.onProgress?.("Enabling IPAM delegated administrator");
75
+ return enableIpamDelegatedAdmin(ec2Client, config.platformAccountId);
76
+ }, phasesCompleted, errors, callbacks);
77
+ // Phase 7: Configure backup settings
78
+ await executePhase("configure-backup", () => {
79
+ callbacks?.onProgress?.("Updating backup global settings");
80
+ return updateBackupGlobalSettings(backupClient);
81
+ }, phasesCompleted, errors, callbacks);
82
+ // Phase 8: Create missing accounts
83
+ callbacks?.onPhaseStart?.("create-accounts");
84
+ callbacks?.onProgress?.("Checking for missing accounts");
85
+ const accountsResult = await createMissingAccounts(orgsClient, config.accounts, createdAccounts);
86
+ if (!accountsResult.success) {
87
+ errors.push({
88
+ phase: "create-accounts",
89
+ error: accountsResult.error.message
90
+ });
91
+ callbacks?.onError?.("create-accounts", accountsResult.error);
92
+ callbacks?.onPhaseComplete?.("create-accounts", "error");
93
+ }
94
+ else {
95
+ phasesCompleted.push("create-accounts");
96
+ callbacks?.onPhaseComplete?.("create-accounts", "completed");
97
+ }
98
+ // Phase 9: Ensure organisational units exist
99
+ let ouMap = {};
100
+ callbacks?.onPhaseStart?.("create-organisational-units");
101
+ callbacks?.onProgress?.("Ensuring organisational units exist");
102
+ const ouResult = await ensureOrganisationalUnitsExist(orgsClient, rootId, config.organisationalUnits);
103
+ if (!ouResult.success) {
104
+ errors.push({
105
+ phase: "create-organisational-units",
106
+ error: ouResult.error.message
107
+ });
108
+ callbacks?.onError?.("create-organisational-units", ouResult.error);
109
+ callbacks?.onPhaseComplete?.("create-organisational-units", "error");
110
+ }
111
+ else {
112
+ ouMap = ouResult.data;
113
+ phasesCompleted.push("create-organisational-units");
114
+ callbacks?.onPhaseComplete?.("create-organisational-units", "completed");
115
+ }
116
+ // Phase 10: Place accounts in OUs
117
+ if (Object.keys(ouMap).length > 0 && config.accountPlacements) {
118
+ const accountInfos = buildAccountInfos(config.accountPlacements);
119
+ await executePhase("place-accounts", () => {
120
+ callbacks?.onProgress?.("Placing accounts in organisational units");
121
+ return placeAccountsInOUs(orgsClient, ouMap, accountInfos);
122
+ }, phasesCompleted, errors, callbacks);
123
+ }
124
+ else {
125
+ phasesSkipped.push("place-accounts");
126
+ callbacks?.onPhaseStart?.("place-accounts");
127
+ callbacks?.onPhaseComplete?.("place-accounts", "skipped");
128
+ }
129
+ // Phase 11: Activate cost allocation tags
130
+ const tags = config.costAllocationTags ?? [];
131
+ if (tags.length > 0) {
132
+ await executePhase("activate-cost-tags", () => {
133
+ callbacks?.onProgress?.("Activating cost allocation tags");
134
+ return activateCostAllocationTags(ceClient, tags.map((t) => ({ TagKey: t })));
135
+ }, phasesCompleted, errors, callbacks);
136
+ }
137
+ else {
138
+ phasesSkipped.push("activate-cost-tags");
139
+ callbacks?.onPhaseStart?.("activate-cost-tags");
140
+ callbacks?.onPhaseComplete?.("activate-cost-tags", "skipped");
141
+ }
142
+ // Phase 12: Check Identity Centre
143
+ if (config.skipIdentityCentre) {
144
+ phasesSkipped.push("check-identity-centre");
145
+ callbacks?.onPhaseStart?.("check-identity-centre");
146
+ callbacks?.onPhaseComplete?.("check-identity-centre", "skipped");
147
+ }
148
+ else {
149
+ callbacks?.onPhaseStart?.("check-identity-centre");
150
+ callbacks?.onProgress?.("Checking Identity Centre status");
151
+ const icResult = await checkIdentityCentreStatus(ssoClient);
152
+ if (!icResult.success) {
153
+ errors.push({
154
+ phase: "check-identity-centre",
155
+ error: icResult.error.message
156
+ });
157
+ callbacks?.onError?.("check-identity-centre", icResult.error);
158
+ callbacks?.onPhaseComplete?.("check-identity-centre", "error");
159
+ }
160
+ else {
161
+ identityCentreStatus = icResult.data.enabled ? "enabled" : "not-enabled";
162
+ phasesCompleted.push("check-identity-centre");
163
+ callbacks?.onPhaseComplete?.("check-identity-centre", "completed");
164
+ }
165
+ }
166
+ return success({
167
+ organisationId: orgId,
168
+ createdAccounts,
169
+ identityCentreStatus,
170
+ phasesCompleted,
171
+ phasesSkipped,
172
+ errors
173
+ });
174
+ }
175
+ /**
176
+ * Execute a single phase with standard callback handling.
177
+ * Records success or error; never throws.
178
+ */
179
+ async function executePhase(phase, fn, phasesCompleted, errors, callbacks) {
180
+ callbacks?.onPhaseStart?.(phase);
181
+ const result = await fn();
182
+ if (!result.success) {
183
+ errors.push({ phase, error: result.error.message });
184
+ callbacks?.onError?.(phase, result.error);
185
+ callbacks?.onPhaseComplete?.(phase, "error");
186
+ }
187
+ else {
188
+ phasesCompleted.push(phase);
189
+ callbacks?.onPhaseComplete?.(phase, "completed");
190
+ }
191
+ }
192
+ /**
193
+ * List existing accounts, then create any that are missing by name.
194
+ */
195
+ async function createMissingAccounts(client, desiredAccounts, createdAccounts) {
196
+ const listResult = await listAccounts(client);
197
+ if (!listResult.success) {
198
+ return failure(listResult.error);
199
+ }
200
+ const existingNames = new Set(listResult.data
201
+ .map((a) => a.Name?.toLowerCase())
202
+ .filter((n) => n !== undefined));
203
+ for (const desired of desiredAccounts) {
204
+ if (existingNames.has(desired.name.toLowerCase())) {
205
+ continue;
206
+ }
207
+ const createResult = await createAccount(client, desired.name, desired.email);
208
+ if (!createResult.success) {
209
+ return failure(createResult.error);
210
+ }
211
+ createdAccounts.push({
212
+ name: createResult.data.accountName,
213
+ accountId: createResult.data.accountId
214
+ });
215
+ }
216
+ return success(undefined);
217
+ }
218
+ /**
219
+ * Convert account placement config into AccountInfo[] for placeAccountsInOUs.
220
+ */
221
+ function buildAccountInfos(placements) {
222
+ return Object.entries(placements).map(([accountId, environment]) => ({
223
+ id: accountId,
224
+ name: accountId,
225
+ environment
226
+ }));
227
+ }
@@ -0,0 +1,10 @@
1
+ import { type Result } from "@fjall/generator";
2
+ import type { DeploymentOperation } from "../types/operations.js";
3
+ /**
4
+ * Determine the deployment operation type from the target string and filesystem.
5
+ *
6
+ * - If target matches an organisation type → OrganisationOperation
7
+ * - If fjall/<target> directory exists → ApplicationOperation
8
+ * - Otherwise → failure
9
+ */
10
+ export declare function resolveOperation(target: string, workingDirectory: string): Promise<Result<DeploymentOperation>>;
@@ -0,0 +1,53 @@
1
+ import { join, relative } from "path";
2
+ import { success, failure } from "@fjall/generator";
3
+ import { ORGANISATION_TYPES } from "../types/operations.js";
4
+ import { fileExists } from "../util/fsHelpers.js";
5
+ const ORGANISATION_TYPE_VALUES = new Set(Object.values(ORGANISATION_TYPES));
6
+ const TARGET_PATTERN = /^[a-z][a-z0-9-]*$/;
7
+ /**
8
+ * Determine the deployment operation type from the target string and filesystem.
9
+ *
10
+ * - If target matches an organisation type → OrganisationOperation
11
+ * - If fjall/<target> directory exists → ApplicationOperation
12
+ * - Otherwise → failure
13
+ */
14
+ export async function resolveOperation(target, workingDirectory) {
15
+ // Check for organisation-level deployment
16
+ const normalisedTarget = target.toLowerCase();
17
+ if (ORGANISATION_TYPE_VALUES.has(normalisedTarget)) {
18
+ return success({
19
+ kind: "organisation",
20
+ type: normalisedTarget,
21
+ target,
22
+ path: workingDirectory
23
+ });
24
+ }
25
+ // Check for account-prefixed targets (e.g., "account-prod")
26
+ if (normalisedTarget.startsWith("account")) {
27
+ return success({
28
+ kind: "organisation",
29
+ type: ORGANISATION_TYPES.ACCOUNT,
30
+ target,
31
+ path: workingDirectory
32
+ });
33
+ }
34
+ // Validate target before joining into filesystem path
35
+ if (!TARGET_PATTERN.test(target)) {
36
+ return failure(new Error(`Invalid target "${target}": must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens`));
37
+ }
38
+ // Check for application deployment
39
+ const appPath = join(workingDirectory, "fjall", target);
40
+ // Defence-in-depth: ensure resolved path stays under workingDirectory
41
+ const rel = relative(workingDirectory, appPath);
42
+ if (rel.startsWith("..")) {
43
+ return failure(new Error(`Invalid target "${target}": resolved path escapes working directory`));
44
+ }
45
+ if (await fileExists(appPath)) {
46
+ return success({
47
+ kind: "application",
48
+ appName: target,
49
+ path: appPath
50
+ });
51
+ }
52
+ return failure(new Error(`Target "${target}" is not a recognised organisation type and no directory found at fjall/${target}`));
53
+ }
@@ -0,0 +1,15 @@
1
+ import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
2
+ import { CdkService } from "../services/infrastructure/CdkService.js";
3
+ import { CloudFormationService } from "../services/infrastructure/CloudFormationService.js";
4
+ import { ApplicationStackService } from "../services/application/ApplicationStackService.js";
5
+ import { TemplateHashService } from "../services/supporting/TemplateHashService.js";
6
+ import type { DeployParams } from "../types/params.js";
7
+ export interface DeployServices {
8
+ awsProvider: SimpleAwsProvider;
9
+ cdkService: CdkService;
10
+ cfnService: CloudFormationService;
11
+ stackService: ApplicationStackService;
12
+ hashService: TemplateHashService;
13
+ }
14
+ /** CDK needs AWS credentials as env vars, so exportToEnv() is called immediately. */
15
+ export declare function createDeployServices(params: DeployParams): DeployServices;
@@ -0,0 +1,16 @@
1
+ import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
2
+ import { CdkService } from "../services/infrastructure/CdkService.js";
3
+ import { CloudFormationService } from "../services/infrastructure/CloudFormationService.js";
4
+ import { ApplicationStackService } from "../services/application/ApplicationStackService.js";
5
+ import { TemplateHashService } from "../services/supporting/TemplateHashService.js";
6
+ /** CDK needs AWS credentials as env vars, so exportToEnv() is called immediately. */
7
+ export function createDeployServices(params) {
8
+ const awsProvider = new SimpleAwsProvider(params.awsCredentials);
9
+ awsProvider.exportToEnv();
10
+ const cdkService = new CdkService();
11
+ const cfnService = new CloudFormationService(awsProvider);
12
+ const stackService = new ApplicationStackService(cdkService, cfnService);
13
+ stackService.setAwsContext(awsProvider);
14
+ const hashService = new TemplateHashService();
15
+ return { awsProvider, cdkService, cfnService, stackService, hashService };
16
+ }
@@ -0,0 +1,93 @@
1
+ import type { CdkService } from "../infrastructure/CdkService.js";
2
+ import type { CloudFormationService } from "../infrastructure/CloudFormationService.js";
3
+ import type { AwsProvider } from "../../aws/AwsProvider.js";
4
+ import type { ResourceEvent } from "../../types/events.js";
5
+ import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
6
+ import { type ApplicationStack } from "../../types/operations.js";
7
+ import type { ParallelDeploymentResult } from "../../types/deployment/parallel.js";
8
+ import { type AppResourceFlags } from "../../types/patternDetection.js";
9
+ import { type Result } from "@fjall/generator";
10
+ import { ApplicationError, type StackDeploymentData } from "../../types/application/ApplicationServiceTypes.js";
11
+ /**
12
+ * Service for deploying and destroying individual CloudFormation stacks.
13
+ *
14
+ * Handles single-stack deploy/destroy, parallel operations, and the full
15
+ * destroy-all-stacks orchestration.
16
+ *
17
+ * Unlike the CLI version, services are constructor-injected rather than
18
+ * fetched via singleton factories.
19
+ */
20
+ export declare class ApplicationStackService {
21
+ private cdkService;
22
+ private cloudFormationService;
23
+ private aws;
24
+ constructor(cdkService: CdkService, cloudFormationService: CloudFormationService);
25
+ setAwsContext(aws: AwsProvider | undefined): void;
26
+ /**
27
+ * Deploy a specific stack type for an application
28
+ */
29
+ deployStack(stackType: ApplicationStack, context: DeploymentContext, callbacks?: {
30
+ onOutput?: (chunk: string) => void;
31
+ onResourceProgress?: (event: ResourceEvent) => void;
32
+ }): Promise<Result<StackDeploymentData, ApplicationError>>;
33
+ /**
34
+ * Deploy multiple stacks in parallel within a deployment phase.
35
+ *
36
+ * Uses Promise.allSettled to ensure all stacks are attempted even if some fail.
37
+ */
38
+ deployStacksInParallel(stacks: readonly ApplicationStack[], context: DeploymentContext, callbacks?: {
39
+ onOutput?: (chunk: string, stackId: ApplicationStack) => void;
40
+ onResourceProgress?: (event: ResourceEvent, stackId: ApplicationStack) => void;
41
+ onStackComplete?: (stack: ApplicationStack, success: boolean, duration: number, error?: Error) => void;
42
+ }): Promise<Result<ParallelDeploymentResult[], ApplicationError>>;
43
+ /**
44
+ * Destroy a specific stack type
45
+ */
46
+ destroyStack(stackType: ApplicationStack, context: DeploymentContext, callbacks?: {
47
+ onOutput?: (chunk: string) => void;
48
+ onResourceProgress?: (event: ResourceEvent) => void;
49
+ }, useCdkOut?: boolean): Promise<Result<StackDeploymentData, ApplicationError>>;
50
+ /**
51
+ * Destroy multiple stacks in parallel within a destroy phase.
52
+ *
53
+ * IMPORTANT: For parallel operations, useCdkOut must be true and CDK synth must
54
+ * be run beforehand. Multiple CDK commands cannot run in parallel because they
55
+ * all try to synthesise to the same cdk.out directory.
56
+ */
57
+ destroyStacksInParallel(stacks: readonly ApplicationStack[], context: DeploymentContext, callbacks?: {
58
+ onOutput?: (chunk: string, stackId: ApplicationStack) => void;
59
+ onResourceProgress?: (event: ResourceEvent, stackId: ApplicationStack) => void;
60
+ onStackComplete?: (stack: ApplicationStack, success: boolean, duration: number, error?: Error) => void;
61
+ }, useCdkOut?: boolean): Promise<Result<ParallelDeploymentResult[], ApplicationError>>;
62
+ /**
63
+ * Get stack outputs for a deployed stack
64
+ */
65
+ getStackOutputs(appName: string, stackType: ApplicationStack): Promise<Result<Record<string, string | number | boolean>, ApplicationError>>;
66
+ /**
67
+ * Resolve the website URL for an application from CloudFormation stack outputs.
68
+ * Checks Compute stack for LoadBalancer URL, then CDN stack for CloudFront distribution.
69
+ */
70
+ resolveWebsiteUrl(appName: string): Promise<string | undefined>;
71
+ /**
72
+ * Destroy all stacks for an application in reverse order.
73
+ * Supports parallel destruction for OpenNext patterns (nextjs/payload).
74
+ */
75
+ destroyAllStacks(context: DeploymentContext, callbacks?: {
76
+ onOutput?: (chunk: string, stackId?: ApplicationStack) => void;
77
+ onResourceProgress?: (event: ResourceEvent, stackId?: ApplicationStack) => void;
78
+ onStackStart?: (stackType: ApplicationStack, stackName: string) => void;
79
+ onStackComplete?: (stackType: ApplicationStack, result: Result<StackDeploymentData, ApplicationError>) => void | Promise<void>;
80
+ onParallelPhaseStart?: (stacks: readonly ApplicationStack[], description: string) => void;
81
+ onParallelPhaseComplete?: (results: ParallelDeploymentResult[]) => void;
82
+ }, resources?: AppResourceFlags): Promise<Result<{
83
+ message: string;
84
+ }, ApplicationError>>;
85
+ /**
86
+ * Convert "doesn't exist" errors to success for destroy operations.
87
+ */
88
+ private convertDestroyResultIfNotExists;
89
+ /**
90
+ * Handle destroy errors and determine if we should continue or fail
91
+ */
92
+ private handleDestroyError;
93
+ }