@fjall/deploy-core 0.94.0 → 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.
- package/dist/.minified +1 -1
- package/dist/src/aws/organisations/accounts.js +1 -99
- package/dist/src/aws/organisations/backup.js +1 -30
- package/dist/src/aws/organisations/costAllocation.js +1 -28
- package/dist/src/aws/organisations/delegatedAdmin.js +3 -43
- package/dist/src/aws/organisations/identityCentre.js +1 -23
- package/dist/src/aws/organisations/ipam.js +1 -20
- package/dist/src/aws/organisations/organisation.js +1 -103
- package/dist/src/aws/organisations/organisationalUnits.js +1 -239
- package/dist/src/aws/organisations/policies.js +1 -37
- package/dist/src/aws/organisations/ram.js +1 -19
- package/dist/src/aws/organisations/serviceAccess.js +1 -44
- package/dist/src/aws/organisations/trustedAccess.js +1 -19
- package/dist/src/aws/utils/regions.js +1 -1
- package/dist/src/index.js +1 -65
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +1 -78
- package/dist/src/orchestration/activeDeploymentGuard.js +5 -39
- package/dist/src/orchestration/applicationDeploy.js +1 -149
- package/dist/src/orchestration/applicationDeployHelpers.js +4 -223
- package/dist/src/orchestration/applicationDestroy.js +1 -131
- package/dist/src/orchestration/builders/dockerBuilder.js +1 -98
- package/dist/src/orchestration/builders/openNextBuilder.js +1 -144
- package/dist/src/orchestration/cascadeHelpers.js +1 -160
- package/dist/src/orchestration/contextHelpers.js +1 -107
- package/dist/src/orchestration/deploy.js +1 -42
- package/dist/src/orchestration/destroy.js +1 -67
- package/dist/src/orchestration/detectionPipeline.js +1 -84
- package/dist/src/orchestration/dockerBuildHelper.js +1 -49
- package/dist/src/orchestration/dockerInterface.js +0 -1
- package/dist/src/orchestration/domainInterface.js +0 -1
- package/dist/src/orchestration/openNextBuild.js +3 -243
- package/dist/src/orchestration/organisationDeploy.js +3 -284
- package/dist/src/orchestration/organisationDestroy.js +3 -189
- package/dist/src/orchestration/organisationSetup.js +1 -247
- package/dist/src/orchestration/resolveOperation.js +1 -123
- package/dist/src/orchestration/welcomeImageHelper.js +1 -64
- package/dist/src/services/application/ApplicationStackService.js +1 -218
- package/dist/src/services/application/applicationStackHelpers.js +4 -248
- package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -244
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -125
- package/dist/src/services/infrastructure/CdkProcessManager.js +3 -278
- package/dist/src/services/infrastructure/CdkService.js +3 -213
- package/dist/src/services/infrastructure/CloudFormationService.js +1 -248
- package/dist/src/services/infrastructure/ICdkProcessManager.js +0 -1
- package/dist/src/services/supporting/CdkContextBuilder.js +1 -44
- package/dist/src/services/supporting/TemplateHashService.js +1 -152
- package/dist/src/steps/stepRegistry.js +1 -505
- package/dist/src/types/apiClient.js +0 -1
- package/dist/src/types/detection.js +0 -1
- package/dist/src/types/frameworkBuilder.js +0 -8
- package/dist/src/types/params.js +0 -1
- package/dist/src/types/patternDetection.js +1 -88
- package/dist/src/types/stepDefinitions.js +1 -98
- package/package.json +4 -4
|
@@ -1,243 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Resolves `@opennextjs/aws` from the app's own node_modules (not from
|
|
5
|
-
* deploy-core's dependencies) and runs the build with timeout handling,
|
|
6
|
-
* stream cleanup, and credential masking.
|
|
7
|
-
*
|
|
8
|
-
* For Payload apps, generates the import map before building.
|
|
9
|
-
*/
|
|
10
|
-
import { createRequire } from "module";
|
|
11
|
-
import { existsSync, statSync, rmSync } from "fs";
|
|
12
|
-
import { join } from "path";
|
|
13
|
-
import { success, failure } from "@fjall/generator";
|
|
14
|
-
import { filterDangerousEnvVars, maskSensitiveOutput, parseShellArgs } from "@fjall/util";
|
|
15
|
-
import { logger } from "@fjall/util/logger";
|
|
16
|
-
import { isOpenNextPattern } from "../types/patternDetection.js";
|
|
17
|
-
import { spawnWithTimeout } from "./spawnHelpers.js";
|
|
18
|
-
const OPENNEXT_PACKAGE = "@opennextjs/aws";
|
|
19
|
-
/** Default build timeout: 10 minutes */
|
|
20
|
-
export const BUILD_TIMEOUT_MS = 600_000;
|
|
21
|
-
/** Timeout for Payload import map generation: 30 seconds */
|
|
22
|
-
export const IMPORT_MAP_TIMEOUT_MS = 30_000;
|
|
23
|
-
/**
|
|
24
|
-
* Resolve the `open-next` binary from the app's own node_modules.
|
|
25
|
-
*
|
|
26
|
-
* Uses `createRequire` anchored to the app path so that the binary
|
|
27
|
-
* comes from the project's dependencies, not deploy-core's.
|
|
28
|
-
*/
|
|
29
|
-
function resolveOpenNextBinary(appPath) {
|
|
30
|
-
const require = createRequire(join(appPath, "package.json"));
|
|
31
|
-
let searchPaths;
|
|
32
|
-
try {
|
|
33
|
-
searchPaths = require.resolve.paths(OPENNEXT_PACKAGE);
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
return failure(new Error(`Cannot determine module search paths for ${OPENNEXT_PACKAGE}. ` +
|
|
37
|
-
`Ensure it is installed in your project. (${err instanceof Error ? err.message : String(err)})`));
|
|
38
|
-
}
|
|
39
|
-
if (!searchPaths || searchPaths.length === 0) {
|
|
40
|
-
return failure(new Error(`Cannot determine module search paths for ${OPENNEXT_PACKAGE}. ` +
|
|
41
|
-
"Ensure it is installed in your project."));
|
|
42
|
-
}
|
|
43
|
-
for (const nodeModulesPath of searchPaths) {
|
|
44
|
-
const packageDir = join(nodeModulesPath, "@opennextjs", "aws");
|
|
45
|
-
try {
|
|
46
|
-
const stat = statSync(packageDir);
|
|
47
|
-
if (stat.isDirectory()) {
|
|
48
|
-
const binPath = join(nodeModulesPath, ".bin", "open-next");
|
|
49
|
-
if (!existsSync(binPath)) {
|
|
50
|
-
logger.debug("openNextBuild", "Package found but binary missing", {
|
|
51
|
-
nodeModulesPath,
|
|
52
|
-
binPath
|
|
53
|
-
});
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
logger.debug("openNextBuild", "Resolved OpenNext binary", {
|
|
57
|
-
binPath
|
|
58
|
-
});
|
|
59
|
-
return success(binPath);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
logger.debug("openNextBuild", "Package not at location", {
|
|
64
|
-
nodeModulesPath,
|
|
65
|
-
error: String(err)
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return failure(new Error(`Cannot find ${OPENNEXT_PACKAGE} package. ` +
|
|
70
|
-
`Ensure it is installed in your project: npm install ${OPENNEXT_PACKAGE}`));
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Clean up the `.open-next/` directory on build failure to prevent
|
|
74
|
-
* stale artefacts from being used by a subsequent CDK synth.
|
|
75
|
-
*/
|
|
76
|
-
function cleanupOpenNextOutput(appPath) {
|
|
77
|
-
const openNextDir = join(appPath, ".open-next");
|
|
78
|
-
if (existsSync(openNextDir)) {
|
|
79
|
-
try {
|
|
80
|
-
rmSync(openNextDir, { recursive: true, force: true });
|
|
81
|
-
logger.debug("openNextBuild", "Cleaned up stale .open-next directory", {
|
|
82
|
-
path: openNextDir
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
logger.debug("openNextBuild", "Failed to clean up .open-next", {
|
|
87
|
-
error: String(err)
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Handle the common error cases (timeout, enoent, spawn_error) from spawnWithTimeout.
|
|
94
|
-
* Returns a failure Result for error cases, or undefined if the result is "success".
|
|
95
|
-
*/
|
|
96
|
-
function handleSpawnError(result, labels, onCleanup, onError) {
|
|
97
|
-
switch (result.type) {
|
|
98
|
-
case "timeout": {
|
|
99
|
-
onCleanup?.();
|
|
100
|
-
onError?.(labels.timeoutMsg);
|
|
101
|
-
return failure(new Error(labels.timeoutMsg));
|
|
102
|
-
}
|
|
103
|
-
case "enoent": {
|
|
104
|
-
onCleanup?.();
|
|
105
|
-
onError?.(labels.enoentMsg);
|
|
106
|
-
return failure(new Error(labels.enoentMsg));
|
|
107
|
-
}
|
|
108
|
-
case "spawn_error": {
|
|
109
|
-
const msg = `${labels.spawnErrorPrefix}: ${result.error.message}`;
|
|
110
|
-
onCleanup?.();
|
|
111
|
-
onError?.(msg);
|
|
112
|
-
return failure(new Error(msg));
|
|
113
|
-
}
|
|
114
|
-
default:
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Generate the Payload CMS import map before running the OpenNext build.
|
|
120
|
-
*/
|
|
121
|
-
async function generatePayloadImportMap(appPath, callbacks) {
|
|
122
|
-
callbacks.onOpenNextProgress?.(maskSensitiveOutput("Generating Payload import map..."));
|
|
123
|
-
const [cmd, ...args] = parseShellArgs("npx payload generate:importmap");
|
|
124
|
-
const safeEnv = filterDangerousEnvVars(globalThis.process.env);
|
|
125
|
-
const result = await spawnWithTimeout({
|
|
126
|
-
command: cmd,
|
|
127
|
-
args,
|
|
128
|
-
cwd: appPath,
|
|
129
|
-
env: { ...safeEnv, FORCE_COLOR: "0" },
|
|
130
|
-
timeout: IMPORT_MAP_TIMEOUT_MS,
|
|
131
|
-
onStdoutData: (output) => {
|
|
132
|
-
callbacks.onOpenNextProgress?.(maskSensitiveOutput(output.trim()));
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
const spawnError = handleSpawnError(result, {
|
|
136
|
-
timeoutMsg: `Payload import map generation timed out after ${IMPORT_MAP_TIMEOUT_MS / 1000} seconds`,
|
|
137
|
-
enoentMsg: "Payload CLI not found. Ensure payload is installed: npm install payload",
|
|
138
|
-
spawnErrorPrefix: "Failed to generate Payload import map"
|
|
139
|
-
});
|
|
140
|
-
if (spawnError)
|
|
141
|
-
return spawnError;
|
|
142
|
-
if (result.type !== "success")
|
|
143
|
-
return failure(new Error("Unexpected spawn result"));
|
|
144
|
-
if (result.code !== 0) {
|
|
145
|
-
const maskedOutput = maskSensitiveOutput(result.stderr || result.stdout);
|
|
146
|
-
return failure(new Error(`Payload import map generation failed (exit code ${result.code}):\n${maskedOutput}`));
|
|
147
|
-
}
|
|
148
|
-
logger.debug("openNextBuild", "Payload import map generated", { appPath });
|
|
149
|
-
callbacks.onOpenNextProgress?.(maskSensitiveOutput("Payload import map generated"));
|
|
150
|
-
return success(undefined);
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Run the OpenNext build for a Next.js or Payload application.
|
|
154
|
-
*
|
|
155
|
-
* Checks pattern and options internally — returns `success(undefined)`
|
|
156
|
-
* when build is not needed (wrong pattern, skipBuild, infraOnly).
|
|
157
|
-
*
|
|
158
|
-
* Must be called BEFORE the detection pipeline and BEFORE the
|
|
159
|
-
* `deployOnly` early-return check, so built artefacts are available
|
|
160
|
-
* for CDK synth.
|
|
161
|
-
*/
|
|
162
|
-
export async function runOpenNextBuild(operation, pattern, callbacks, options) {
|
|
163
|
-
// Guard: skip if not an OpenNext pattern
|
|
164
|
-
if (!isOpenNextPattern(pattern)) {
|
|
165
|
-
return success(undefined);
|
|
166
|
-
}
|
|
167
|
-
// Guard: skip if build explicitly disabled
|
|
168
|
-
if (options?.skipBuild || options?.infraOnly) {
|
|
169
|
-
logger.debug("openNextBuild", "Build skipped", {
|
|
170
|
-
skipBuild: options?.skipBuild,
|
|
171
|
-
infraOnly: options?.infraOnly
|
|
172
|
-
});
|
|
173
|
-
callbacks.onLog?.(options?.infraOnly
|
|
174
|
-
? "Infrastructure-only mode — skipping OpenNext build"
|
|
175
|
-
: "Build skipped (--skip-build)", "info");
|
|
176
|
-
return success(undefined);
|
|
177
|
-
}
|
|
178
|
-
const appPath = operation.path;
|
|
179
|
-
// For Payload apps, generate import map first
|
|
180
|
-
if (pattern === "payload") {
|
|
181
|
-
const importMapResult = await generatePayloadImportMap(appPath, callbacks);
|
|
182
|
-
if (!importMapResult.success) {
|
|
183
|
-
callbacks.onOpenNextBuildError?.(importMapResult.error.message);
|
|
184
|
-
cleanupOpenNextOutput(appPath);
|
|
185
|
-
return failure(importMapResult.error);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// Resolve OpenNext binary from the app's node_modules
|
|
189
|
-
const binaryResult = resolveOpenNextBinary(appPath);
|
|
190
|
-
if (!binaryResult.success) {
|
|
191
|
-
callbacks.onOpenNextBuildError?.(binaryResult.error.message);
|
|
192
|
-
return failure(binaryResult.error);
|
|
193
|
-
}
|
|
194
|
-
const openNextBinary = binaryResult.data;
|
|
195
|
-
// Start build
|
|
196
|
-
callbacks.onOpenNextBuildStart?.();
|
|
197
|
-
callbacks.onOpenNextProgress?.(maskSensitiveOutput("Starting OpenNext build..."));
|
|
198
|
-
const safeEnv = filterDangerousEnvVars(globalThis.process.env);
|
|
199
|
-
const projectNodeModules = join(appPath, "node_modules");
|
|
200
|
-
const result = await spawnWithTimeout({
|
|
201
|
-
command: openNextBinary,
|
|
202
|
-
args: ["build"],
|
|
203
|
-
cwd: appPath,
|
|
204
|
-
env: {
|
|
205
|
-
...safeEnv,
|
|
206
|
-
FORCE_COLOR: "0",
|
|
207
|
-
NODE_PATH: projectNodeModules,
|
|
208
|
-
npm_config_loglevel: "error",
|
|
209
|
-
PNPM_HOME: safeEnv.PNPM_HOME || ""
|
|
210
|
-
},
|
|
211
|
-
timeout: BUILD_TIMEOUT_MS,
|
|
212
|
-
onStdoutData: (output) => {
|
|
213
|
-
callbacks.onOpenNextProgress?.(maskSensitiveOutput(output.trim()));
|
|
214
|
-
},
|
|
215
|
-
onStderrData: (output) => {
|
|
216
|
-
callbacks.onOpenNextProgress?.(maskSensitiveOutput(output.trim()));
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
const spawnError = handleSpawnError(result, {
|
|
220
|
-
timeoutMsg: `OpenNext build timed out after ${BUILD_TIMEOUT_MS / 1000} seconds`,
|
|
221
|
-
enoentMsg: "OpenNext binary not found. Ensure Node.js is installed and in PATH.",
|
|
222
|
-
spawnErrorPrefix: "Failed to spawn OpenNext build"
|
|
223
|
-
}, () => cleanupOpenNextOutput(appPath), (msg) => callbacks.onOpenNextBuildError?.(msg));
|
|
224
|
-
if (spawnError)
|
|
225
|
-
return spawnError;
|
|
226
|
-
if (result.type !== "success")
|
|
227
|
-
return failure(new Error("Unexpected spawn result"));
|
|
228
|
-
if (result.code !== 0) {
|
|
229
|
-
cleanupOpenNextOutput(appPath);
|
|
230
|
-
const maskedOutput = maskSensitiveOutput(result.stderr || result.stdout);
|
|
231
|
-
callbacks.onOpenNextBuildError?.(`OpenNext build failed (exit code ${result.code})`);
|
|
232
|
-
return failure(new Error(`OpenNext build failed (exit code ${result.code}):\n${maskedOutput}`));
|
|
233
|
-
}
|
|
234
|
-
// Verify .open-next directory was created
|
|
235
|
-
const openNextDir = join(appPath, ".open-next");
|
|
236
|
-
if (!existsSync(openNextDir)) {
|
|
237
|
-
callbacks.onOpenNextBuildError?.("OpenNext build completed but .open-next directory not found");
|
|
238
|
-
return failure(new Error("OpenNext build completed but .open-next directory not found"));
|
|
239
|
-
}
|
|
240
|
-
callbacks.onOpenNextBuildComplete?.();
|
|
241
|
-
callbacks.onOpenNextProgress?.(maskSensitiveOutput("OpenNext build completed successfully"));
|
|
242
|
-
return success(undefined);
|
|
243
|
-
}
|
|
1
|
+
import{createRequire as _}from"module";import{existsSync as g,statSync as S,rmSync as $}from"fs";import{join as l}from"path";import{success as f,failure as i}from"@fjall/generator";import{filterDangerousEnvVars as y,maskSensitiveOutput as p,parseShellArgs as v}from"@fjall/util";import{logger as c}from"@fjall/util/logger";import{isOpenNextPattern as b}from"../types/patternDetection.js";import{spawnWithTimeout as E}from"./spawnHelpers.js";const x="@opennextjs/aws",P=6e5,w=3e4;function T(s){const n=_(l(s,"package.json"));let e;try{e=n.resolve.paths(x)}catch(r){return i(new Error(`Cannot determine module search paths for ${x}. Ensure it is installed in your project. (${r instanceof Error?r.message:String(r)})`))}if(!e||e.length===0)return i(new Error(`Cannot determine module search paths for ${x}. Ensure it is installed in your project.`));for(const r of e){const t=l(r,"@opennextjs","aws");try{if(S(t).isDirectory()){const d=l(r,".bin","open-next");if(!g(d)){c.debug("openNextBuild","Package found but binary missing",{nodeModulesPath:r,binPath:d});continue}return c.debug("openNextBuild","Resolved OpenNext binary",{binPath:d}),f(d)}}catch(o){c.debug("openNextBuild","Package not at location",{nodeModulesPath:r,error:String(o)})}}return i(new Error(`Cannot find ${x} package. Ensure it is installed in your project: npm install ${x}`))}function O(s){const n=l(s,".open-next");if(g(n))try{$(n,{recursive:!0,force:!0}),c.debug("openNextBuild","Cleaned up stale .open-next directory",{path:n})}catch(e){c.debug("openNextBuild","Failed to clean up .open-next",{error:String(e)})}}function B(s,n,e,r){switch(s.type){case"timeout":return e?.(),r?.(n.timeoutMsg),i(new Error(n.timeoutMsg));case"enoent":return e?.(),r?.(n.enoentMsg),i(new Error(n.enoentMsg));case"spawn_error":{const t=`${n.spawnErrorPrefix}: ${s.error.message}`;return e?.(),r?.(t),i(new Error(t))}default:return}}async function D(s,n){n.onOpenNextProgress?.(p("Generating Payload import map..."));const[e,...r]=v("npx payload generate:importmap"),t=y(globalThis.process.env),o=await E({command:e,args:r,cwd:s,env:{...t,FORCE_COLOR:"0"},timeout:w,onStdoutData:m=>{n.onOpenNextProgress?.(p(m.trim()))}}),d=B(o,{timeoutMsg:`Payload import map generation timed out after ${w/1e3} seconds`,enoentMsg:"Payload CLI not found. Ensure payload is installed: npm install payload",spawnErrorPrefix:"Failed to generate Payload import map"});if(d)return d;if(o.type!=="success")return i(new Error("Unexpected spawn result"));if(o.code!==0){const m=p(o.stderr||o.stdout);return i(new Error(`Payload import map generation failed (exit code ${o.code}):
|
|
2
|
+
${m}`))}return c.debug("openNextBuild","Payload import map generated",{appPath:s}),n.onOpenNextProgress?.(p("Payload import map generated")),f(void 0)}async function H(s,n,e,r){if(!b(n))return f(void 0);if(r?.skipBuild||r?.infraOnly)return c.debug("openNextBuild","Build skipped",{skipBuild:r?.skipBuild,infraOnly:r?.infraOnly}),e.onLog?.(r?.infraOnly?"Infrastructure-only mode \u2014 skipping OpenNext build":"Build skipped (--skip-build)","info"),f(void 0);const t=s.path;if(n==="payload"){const u=await D(t,e);if(!u.success)return e.onOpenNextBuildError?.(u.error.message),O(t),i(u.error)}const o=T(t);if(!o.success)return e.onOpenNextBuildError?.(o.error.message),i(o.error);const d=o.data;e.onOpenNextBuildStart?.(),e.onOpenNextProgress?.(p("Starting OpenNext build..."));const m=y(globalThis.process.env),M=l(t,"node_modules"),a=await E({command:d,args:["build"],cwd:t,env:{...m,FORCE_COLOR:"0",NODE_PATH:M,npm_config_loglevel:"error",PNPM_HOME:m.PNPM_HOME||""},timeout:P,onStdoutData:u=>{e.onOpenNextProgress?.(p(u.trim()))},onStderrData:u=>{e.onOpenNextProgress?.(p(u.trim()))}}),N=B(a,{timeoutMsg:`OpenNext build timed out after ${P/1e3} seconds`,enoentMsg:"OpenNext binary not found. Ensure Node.js is installed and in PATH.",spawnErrorPrefix:"Failed to spawn OpenNext build"},()=>O(t),u=>e.onOpenNextBuildError?.(u));if(N)return N;if(a.type!=="success")return i(new Error("Unexpected spawn result"));if(a.code!==0){O(t);const u=p(a.stderr||a.stdout);return e.onOpenNextBuildError?.(`OpenNext build failed (exit code ${a.code})`),i(new Error(`OpenNext build failed (exit code ${a.code}):
|
|
3
|
+
${u}`))}const h=l(t,".open-next");return g(h)?(e.onOpenNextBuildComplete?.(),e.onOpenNextProgress?.(p("OpenNext build completed successfully")),f(void 0)):(e.onOpenNextBuildError?.("OpenNext build completed but .open-next directory not found"),i(new Error("OpenNext build completed but .open-next directory not found")))}export{P as BUILD_TIMEOUT_MS,w as IMPORT_MAP_TIMEOUT_MS,H as runOpenNextBuild};
|
|
@@ -1,284 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { stubCallerIdentity } from "../types/deployment/index.js";
|
|
5
|
-
import { buildParamsContext, collectStackOutputs, synthOrFail, bootstrapOrFail, forwardOutput, forwardResourceProgress } from "./contextHelpers.js";
|
|
6
|
-
import { partitionAccounts, deployCascadeAccount, readPlatformIpamPoolIds, deployDomains } from "./cascadeHelpers.js";
|
|
7
|
-
import { maskSensitiveOutput } from "@fjall/util";
|
|
8
|
-
import { INFRA_STEP_NAME, STEP_IDS } from "../types/stepDefinitions.js";
|
|
9
|
-
/**
|
|
10
|
-
* Organisation deployment orchestration.
|
|
11
|
-
*
|
|
12
|
-
* Handles three target types:
|
|
13
|
-
* - organisation: deploy org infra + cascade to platform + all accounts
|
|
14
|
-
* - platform: deploy platform stack only
|
|
15
|
-
* - account: deploy single account stack
|
|
16
|
-
*
|
|
17
|
-
* Auth and org setup (creating AWS accounts/OUs) are the caller's
|
|
18
|
-
* responsibility. deploy-core receives credentials and deploys.
|
|
19
|
-
*/
|
|
20
|
-
export async function deployOrganisation(params, services, operation) {
|
|
21
|
-
const startTime = Date.now();
|
|
22
|
-
switch (operation.type) {
|
|
23
|
-
case ORGANISATION_TYPES.ORGANISATION:
|
|
24
|
-
return deployOrgWithCascade(params, services, operation, startTime);
|
|
25
|
-
case ORGANISATION_TYPES.PLATFORM:
|
|
26
|
-
return deploySingleComponent(params, services, operation, "platform", startTime);
|
|
27
|
-
case ORGANISATION_TYPES.ACCOUNT:
|
|
28
|
-
return deploySingleComponent(params, services, operation, "account", startTime);
|
|
29
|
-
default: {
|
|
30
|
-
const _exhaustive = operation.type;
|
|
31
|
-
return failure(new Error(`Unsupported organisation type: ${String(_exhaustive)}`));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Build a deployment context for an organisation component.
|
|
37
|
-
*/
|
|
38
|
-
function buildOrgContext(params, services, operation, deployType, accountName) {
|
|
39
|
-
return CdkContextBuilder.buildDeploymentContext({
|
|
40
|
-
deployType,
|
|
41
|
-
target: operation.target,
|
|
42
|
-
path: operation.path,
|
|
43
|
-
region: services.awsProvider.getRegion(),
|
|
44
|
-
accountName,
|
|
45
|
-
callerIdentity: stubCallerIdentity(services.awsProvider.getAccountId()),
|
|
46
|
-
...buildParamsContext({
|
|
47
|
-
orgConfig: params.orgConfig,
|
|
48
|
-
identity: params.identity,
|
|
49
|
-
skipOidc: params.options?.skipOidc
|
|
50
|
-
})
|
|
51
|
-
}, {
|
|
52
|
-
verbose: params.options?.verbose,
|
|
53
|
-
infraOnly: params.options?.infraOnly
|
|
54
|
-
}, params.orgConfig);
|
|
55
|
-
}
|
|
56
|
-
const INFRA_STEPS = {
|
|
57
|
-
CONNECT: { id: STEP_IDS.CONNECT, name: INFRA_STEP_NAME.CONNECT },
|
|
58
|
-
PREPARE: { id: STEP_IDS.PREPARE_ENVIRONMENT, name: INFRA_STEP_NAME.PREPARE },
|
|
59
|
-
DEPLOY: { id: STEP_IDS.DEPLOY, name: INFRA_STEP_NAME.DEPLOY },
|
|
60
|
-
MONITORING: { id: STEP_IDS.MONITORING, name: INFRA_STEP_NAME.MONITORING },
|
|
61
|
-
ORG_DEPLOY: {
|
|
62
|
-
id: STEP_IDS.ORG_DEPLOY,
|
|
63
|
-
name: "Deploying organisation infrastructure"
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
const INFRA_STEP_TOTAL = 4;
|
|
67
|
-
/**
|
|
68
|
-
* Deploy a single organisation component (platform or account).
|
|
69
|
-
*
|
|
70
|
-
* Emits 4 named steps from INFRASTRUCTURE_STEP_NAMES: Connect securely →
|
|
71
|
-
* Prepare environment → Deploy infrastructure → Enable monitoring.
|
|
72
|
-
*/
|
|
73
|
-
async function deploySingleComponent(params, services, operation, deployType, startTime) {
|
|
74
|
-
const { callbacks } = params;
|
|
75
|
-
// Step 1: Connect securely — already seeded by the webapp trigger.
|
|
76
|
-
// Complete it now: credentials are available by the time deploy-core runs.
|
|
77
|
-
callbacks.onStepComplete?.(INFRA_STEPS.CONNECT.id, INFRA_STEPS.CONNECT.name, "completed", 0, INFRA_STEP_TOTAL);
|
|
78
|
-
// Step 2: Prepare environment (synth + bootstrap)
|
|
79
|
-
callbacks.onStepStart?.(INFRA_STEPS.PREPARE.id, INFRA_STEPS.PREPARE.name, 1, INFRA_STEP_TOTAL);
|
|
80
|
-
const context = buildOrgContext(params, services, operation, deployType, deployType === "account" ? operation.target : undefined);
|
|
81
|
-
// Synth
|
|
82
|
-
callbacks.onLog?.(`Synthesising ${deployType} infrastructure…`, "info");
|
|
83
|
-
const synthResult = await synthOrFail(services, context, callbacks, "CDK synthesis failed");
|
|
84
|
-
if (!synthResult.success) {
|
|
85
|
-
callbacks.onStepComplete?.(INFRA_STEPS.PREPARE.id, INFRA_STEPS.PREPARE.name, "error", 1, INFRA_STEP_TOTAL);
|
|
86
|
-
return synthResult;
|
|
87
|
-
}
|
|
88
|
-
// Bootstrap
|
|
89
|
-
const bsResult = await bootstrapOrFail(services, context, callbacks);
|
|
90
|
-
if (!bsResult.success) {
|
|
91
|
-
callbacks.onStepComplete?.(INFRA_STEPS.PREPARE.id, INFRA_STEPS.PREPARE.name, "error", 1, INFRA_STEP_TOTAL);
|
|
92
|
-
return bsResult;
|
|
93
|
-
}
|
|
94
|
-
callbacks.onStepComplete?.(INFRA_STEPS.PREPARE.id, INFRA_STEPS.PREPARE.name, "completed", 1, INFRA_STEP_TOTAL);
|
|
95
|
-
// Step 3: Deploy infrastructure
|
|
96
|
-
const stackName = getOrganisationStackName(operation.type);
|
|
97
|
-
callbacks.onStepStart?.(INFRA_STEPS.DEPLOY.id, INFRA_STEPS.DEPLOY.name, 2, INFRA_STEP_TOTAL);
|
|
98
|
-
const deployResult = await services.cdkService.runCdkDeploy(context, stackName, forwardOutput(callbacks), forwardResourceProgress(callbacks), services.awsProvider);
|
|
99
|
-
if (!deployResult.success) {
|
|
100
|
-
callbacks.onStepComplete?.(INFRA_STEPS.DEPLOY.id, INFRA_STEPS.DEPLOY.name, "error", 2, INFRA_STEP_TOTAL);
|
|
101
|
-
const error = new Error(maskSensitiveOutput(deployResult.error));
|
|
102
|
-
callbacks.onError?.(error);
|
|
103
|
-
return failure(error);
|
|
104
|
-
}
|
|
105
|
-
callbacks.onStepComplete?.(INFRA_STEPS.DEPLOY.id, INFRA_STEPS.DEPLOY.name, "completed", 2, INFRA_STEP_TOTAL);
|
|
106
|
-
// Capture CloudFormation outputs (OIDC role ARN, etc.)
|
|
107
|
-
const outputsResult = await services.cfnService.getStackOutputs(stackName);
|
|
108
|
-
if (!outputsResult.success) {
|
|
109
|
-
callbacks.onLog?.("Failed to read stack outputs (non-critical)", "debug");
|
|
110
|
-
}
|
|
111
|
-
const outputs = collectStackOutputs(outputsResult);
|
|
112
|
-
// Step 4: Enable monitoring — CloudTrail + alarms are part of the
|
|
113
|
-
// Account stack. Signal post-deploy readiness.
|
|
114
|
-
callbacks.onStepStart?.(INFRA_STEPS.MONITORING.id, INFRA_STEPS.MONITORING.name, 3, INFRA_STEP_TOTAL);
|
|
115
|
-
callbacks.onStepComplete?.(INFRA_STEPS.MONITORING.id, INFRA_STEPS.MONITORING.name, "completed", 3, INFRA_STEP_TOTAL);
|
|
116
|
-
return success({
|
|
117
|
-
target: operation.target,
|
|
118
|
-
deploymentType: "organisation",
|
|
119
|
-
outputs,
|
|
120
|
-
durationMs: Date.now() - startTime
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Full organisation deployment with cascade to platform + member accounts.
|
|
125
|
-
*/
|
|
126
|
-
async function deployOrgWithCascade(params, services, operation, startTime) {
|
|
127
|
-
const { callbacks, options } = params;
|
|
128
|
-
const providerAccounts = params.orgConfig?.providerAccounts ?? [];
|
|
129
|
-
const context = buildOrgContext(params, services, operation, "organisation");
|
|
130
|
-
// Compute step counts upfront so pre-deploy failures can reference them
|
|
131
|
-
const cascadeEnabled = options?.cascade !== false;
|
|
132
|
-
const cascadeAccountCount = cascadeEnabled ? providerAccounts.length : 0;
|
|
133
|
-
// Steps: prepare (synth+bootstrap) + org-deploy + cascade accounts
|
|
134
|
-
const totalSteps = 2 + cascadeAccountCount;
|
|
135
|
-
const { id: prepareStepId, name: prepareStepName } = INFRA_STEPS.PREPARE;
|
|
136
|
-
// Step 0: Prepare (synth + bootstrap)
|
|
137
|
-
callbacks.onStepStart?.(prepareStepId, prepareStepName, 0, totalSteps);
|
|
138
|
-
// Synth
|
|
139
|
-
callbacks.onLog?.("Synthesising organisation infrastructure…", "info");
|
|
140
|
-
const synthResult = await synthOrFail(services, context, callbacks, "CDK synthesis failed");
|
|
141
|
-
if (!synthResult.success) {
|
|
142
|
-
callbacks.onStepComplete?.(prepareStepId, prepareStepName, "error", 0, totalSteps);
|
|
143
|
-
return synthResult;
|
|
144
|
-
}
|
|
145
|
-
// Bootstrap org account
|
|
146
|
-
const bsResult = await bootstrapOrFail(services, context, callbacks);
|
|
147
|
-
if (!bsResult.success) {
|
|
148
|
-
callbacks.onStepComplete?.(prepareStepId, prepareStepName, "error", 0, totalSteps);
|
|
149
|
-
return bsResult;
|
|
150
|
-
}
|
|
151
|
-
callbacks.onStepComplete?.(prepareStepId, prepareStepName, "completed", 0, totalSteps);
|
|
152
|
-
// Step 1: Deploy org infrastructure
|
|
153
|
-
const { id: orgStepId, name: orgStepName } = INFRA_STEPS.ORG_DEPLOY;
|
|
154
|
-
callbacks.onStepStart?.(orgStepId, orgStepName, 1, totalSteps);
|
|
155
|
-
const orgStackName = getOrganisationStackName(ORGANISATION_TYPES.ORGANISATION);
|
|
156
|
-
const orgResult = await services.cdkService.runCdkDeploy(context, orgStackName, forwardOutput(callbacks), forwardResourceProgress(callbacks), services.awsProvider);
|
|
157
|
-
if (!orgResult.success) {
|
|
158
|
-
callbacks.onStepComplete?.(orgStepId, orgStepName, "error", 1, totalSteps);
|
|
159
|
-
const error = new Error(maskSensitiveOutput(orgResult.error));
|
|
160
|
-
callbacks.onError?.(error);
|
|
161
|
-
return failure(error);
|
|
162
|
-
}
|
|
163
|
-
// Capture org root stack outputs (OIDC role ARN, etc.)
|
|
164
|
-
const orgOutputsResult = await services.cfnService.getStackOutputs(orgStackName);
|
|
165
|
-
if (!orgOutputsResult.success) {
|
|
166
|
-
callbacks.onLog?.("Failed to read org stack outputs (non-critical)", "debug");
|
|
167
|
-
}
|
|
168
|
-
const orgOutputs = collectStackOutputs(orgOutputsResult);
|
|
169
|
-
callbacks.onStepComplete?.(orgStepId, orgStepName, "completed", 1, totalSteps);
|
|
170
|
-
// Cascade to platform + domains + member accounts
|
|
171
|
-
const cascadeErrors = [];
|
|
172
|
-
const allCascadeOutputs = [];
|
|
173
|
-
if (cascadeEnabled && providerAccounts.length > 0) {
|
|
174
|
-
callbacks.onCascadeStart?.();
|
|
175
|
-
let accountsDeployed = 0;
|
|
176
|
-
let platformDeployed = false;
|
|
177
|
-
let domainsDeployed = false;
|
|
178
|
-
// Phase 1: Deploy platform account
|
|
179
|
-
const { platformAccount, memberAccounts } = partitionAccounts(providerAccounts);
|
|
180
|
-
if (platformAccount) {
|
|
181
|
-
callbacks.onCascadePhaseStart?.("platform");
|
|
182
|
-
const platformResult = await deployCascadeAccount(params, services, operation, platformAccount, "platform", callbacks);
|
|
183
|
-
if (platformResult.success) {
|
|
184
|
-
platformDeployed = true;
|
|
185
|
-
if (platformResult.data.outputs) {
|
|
186
|
-
allCascadeOutputs.push({
|
|
187
|
-
accountId: platformAccount.id,
|
|
188
|
-
outputs: platformResult.data.outputs
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
cascadeErrors.push({
|
|
194
|
-
accountId: platformAccount.id,
|
|
195
|
-
error: platformResult.error.message
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
callbacks.onCascadePhaseComplete?.("platform");
|
|
199
|
-
}
|
|
200
|
-
// Phase 1.5: Read Platform stack outputs for IPAM pool IDs
|
|
201
|
-
let ipamPoolIds = new Map();
|
|
202
|
-
if (platformDeployed && platformAccount) {
|
|
203
|
-
ipamPoolIds = await readPlatformIpamPoolIds(services, platformAccount, callbacks);
|
|
204
|
-
}
|
|
205
|
-
// Phase 2: Deploy domains (apex sequential, delegated parallel)
|
|
206
|
-
if (params.domainProvider) {
|
|
207
|
-
const domainResult = await deployDomains(params.domainProvider, callbacks);
|
|
208
|
-
domainsDeployed = domainResult.domainsDeployed > 0;
|
|
209
|
-
for (const err of domainResult.errors) {
|
|
210
|
-
cascadeErrors.push({
|
|
211
|
-
accountId: "domains",
|
|
212
|
-
error: maskSensitiveOutput(err)
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
// Phase 3: Deploy member accounts in parallel
|
|
217
|
-
if (memberAccounts.length > 0) {
|
|
218
|
-
callbacks.onCascadePhaseStart?.("accounts");
|
|
219
|
-
const region = services.awsProvider.getRegion();
|
|
220
|
-
const memberSettled = await Promise.allSettled(memberAccounts.map((account) => {
|
|
221
|
-
const regionSuffix = region.replace(/-/g, "");
|
|
222
|
-
const ipamPoolId = ipamPoolIds.get(`${account.id}-${regionSuffix}`);
|
|
223
|
-
return deployCascadeAccount(params, services, operation, account, "account", callbacks, ipamPoolId);
|
|
224
|
-
}));
|
|
225
|
-
memberSettled.forEach((settled, j) => {
|
|
226
|
-
const account = memberAccounts[j];
|
|
227
|
-
if (!account)
|
|
228
|
-
return;
|
|
229
|
-
if (settled.status === "rejected") {
|
|
230
|
-
cascadeErrors.push({
|
|
231
|
-
accountId: account.id,
|
|
232
|
-
error: maskSensitiveOutput(settled.reason instanceof Error
|
|
233
|
-
? settled.reason.message
|
|
234
|
-
: String(settled.reason))
|
|
235
|
-
});
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const result = settled.value;
|
|
239
|
-
if (result.success) {
|
|
240
|
-
accountsDeployed++;
|
|
241
|
-
if (result.data.outputs) {
|
|
242
|
-
allCascadeOutputs.push({
|
|
243
|
-
accountId: account.id,
|
|
244
|
-
outputs: result.data.outputs
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
cascadeErrors.push({
|
|
250
|
-
accountId: account.id,
|
|
251
|
-
error: result.error.message
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
callbacks.onCascadePhaseComplete?.("accounts");
|
|
256
|
-
}
|
|
257
|
-
callbacks.onCascadeComplete?.({
|
|
258
|
-
platformDeployed,
|
|
259
|
-
domainsDeployed,
|
|
260
|
-
accountsDeployed,
|
|
261
|
-
accountsFailed: cascadeErrors.length,
|
|
262
|
-
errors: cascadeErrors
|
|
263
|
-
});
|
|
264
|
-
if (cascadeErrors.length > 0) {
|
|
265
|
-
const errorSummary = cascadeErrors
|
|
266
|
-
.map((e) => ` ${e.accountId}: ${e.error}`)
|
|
267
|
-
.join("\n");
|
|
268
|
-
callbacks.onLog?.(maskSensitiveOutput(`Cascade completed with ${cascadeErrors.length} failure(s):\n${errorSummary}`), "warn");
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
const warnings = cascadeErrors.length > 0
|
|
272
|
-
? cascadeErrors.map((e) => maskSensitiveOutput(`${e.accountId}: ${e.error}`))
|
|
273
|
-
: undefined;
|
|
274
|
-
return success({
|
|
275
|
-
target: operation.target,
|
|
276
|
-
deploymentType: "organisation",
|
|
277
|
-
outputs: orgOutputs,
|
|
278
|
-
...(allCascadeOutputs.length > 0
|
|
279
|
-
? { cascadeOutputs: allCascadeOutputs }
|
|
280
|
-
: {}),
|
|
281
|
-
durationMs: Date.now() - startTime,
|
|
282
|
-
warnings
|
|
283
|
-
});
|
|
284
|
-
}
|
|
1
|
+
import{success as K,failure as M}from"@fjall/generator";import{ORGANISATION_TYPES as h,getOrganisationStackName as U}from"../types/operations.js";import{CdkContextBuilder as Z}from"../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as tt}from"../types/deployment/index.js";import{buildParamsContext as et,collectStackOutputs as v,synthOrFail as B,bootstrapOrFail as V,forwardOutput as W,forwardResourceProgress as q}from"./contextHelpers.js";import{partitionAccounts as ot,deployCascadeAccount as z,readPlatformIpamPoolIds as nt,deployDomains as rt}from"./cascadeHelpers.js";import{maskSensitiveOutput as E}from"@fjall/util";import{INFRA_STEP_NAME as w,STEP_IDS as I}from"../types/stepDefinitions.js";async function ft(n,e,a){const s=Date.now();switch(a.type){case h.ORGANISATION:return at(n,e,a,s);case h.PLATFORM:return J(n,e,a,"platform",s);case h.ACCOUNT:return J(n,e,a,"account",s);default:{const t=a.type;return M(new Error(`Unsupported organisation type: ${String(t)}`))}}}function H(n,e,a,s,t){return Z.buildDeploymentContext({deployType:s,target:a.target,path:a.path,region:e.awsProvider.getRegion(),accountName:t,callerIdentity:tt(e.awsProvider.getAccountId()),...et({orgConfig:n.orgConfig,identity:n.identity,skipOidc:n.options?.skipOidc})},{verbose:n.options?.verbose,infraOnly:n.options?.infraOnly},n.orgConfig)}const o={CONNECT:{id:I.CONNECT,name:w.CONNECT},PREPARE:{id:I.PREPARE_ENVIRONMENT,name:w.PREPARE},DEPLOY:{id:I.DEPLOY,name:w.DEPLOY},MONITORING:{id:I.MONITORING,name:w.MONITORING},ORG_DEPLOY:{id:I.ORG_DEPLOY,name:"Deploying organisation infrastructure"}},d=4;async function J(n,e,a,s,t){const{callbacks:r}=n;r.onStepComplete?.(o.CONNECT.id,o.CONNECT.name,"completed",0,d),r.onStepStart?.(o.PREPARE.id,o.PREPARE.name,1,d);const m=H(n,e,a,s,s==="account"?a.target:void 0);r.onLog?.(`Synthesising ${s} infrastructure\u2026`,"info");const g=await B(e,m,r,"CDK synthesis failed");if(!g.success)return r.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"error",1,d),g;const R=await V(e,m,r);if(!R.success)return r.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"error",1,d),R;r.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"completed",1,d);const D=U(a.type);r.onStepStart?.(o.DEPLOY.id,o.DEPLOY.name,2,d);const u=await e.cdkService.runCdkDeploy(m,D,W(r),q(r),e.awsProvider);if(!u.success){r.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"error",2,d);const C=new Error(E(u.error));return r.onError?.(C),M(C)}r.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"completed",2,d);const f=await e.cfnService.getStackOutputs(D);f.success||r.onLog?.("Failed to read stack outputs (non-critical)","debug");const O=v(f);return r.onStepStart?.(o.MONITORING.id,o.MONITORING.name,3,d),r.onStepComplete?.(o.MONITORING.id,o.MONITORING.name,"completed",3,d),K({target:a.target,deploymentType:"organisation",outputs:O,durationMs:Date.now()-t})}async function at(n,e,a,s){const{callbacks:t,options:r}=n,m=n.orgConfig?.providerAccounts??[],g=H(n,e,a,"organisation"),R=r?.cascade!==!1,u=2+(R?m.length:0),{id:f,name:O}=o.PREPARE;t.onStepStart?.(f,O,0,u),t.onLog?.("Synthesising organisation infrastructure\u2026","info");const C=await B(e,g,t,"CDK synthesis failed");if(!C.success)return t.onStepComplete?.(f,O,"error",0,u),C;const Y=await V(e,g,t);if(!Y.success)return t.onStepComplete?.(f,O,"error",0,u),Y;t.onStepComplete?.(f,O,"completed",0,u);const{id:T,name:L}=o.ORG_DEPLOY;t.onStepStart?.(T,L,1,u);const _=U(h.ORGANISATION),$=await e.cdkService.runCdkDeploy(g,_,W(t),q(t),e.awsProvider);if(!$.success){t.onStepComplete?.(T,L,"error",1,u);const l=new Error(E($.error));return t.onError?.(l),M(l)}const x=await e.cfnService.getStackOutputs(_);x.success||t.onLog?.("Failed to read org stack outputs (non-critical)","debug");const Q=v(x);t.onStepComplete?.(T,L,"completed",1,u);const c=[],y=[];if(R&&m.length>0){t.onCascadeStart?.();let l=0,k=!1,F=!1;const{platformAccount:P,memberAccounts:b}=ot(m);if(P){t.onCascadePhaseStart?.("platform");const i=await z(n,e,a,P,"platform",t);i.success?(k=!0,i.data.outputs&&y.push({accountId:P.id,outputs:i.data.outputs})):c.push({accountId:P.id,error:i.error.message}),t.onCascadePhaseComplete?.("platform")}let j=new Map;if(k&&P&&(j=await nt(e,P,t)),n.domainProvider){const i=await rt(n.domainProvider,t);F=i.domainsDeployed>0;for(const N of i.errors)c.push({accountId:"domains",error:E(N)})}if(b.length>0){t.onCascadePhaseStart?.("accounts");const i=e.awsProvider.getRegion();(await Promise.allSettled(b.map(p=>{const G=i.replace(/-/g,""),S=j.get(`${p.id}-${G}`);return z(n,e,a,p,"account",t,S)}))).forEach((p,G)=>{const S=b[G];if(!S)return;if(p.status==="rejected"){c.push({accountId:S.id,error:E(p.reason instanceof Error?p.reason.message:String(p.reason))});return}const A=p.value;A.success?(l++,A.data.outputs&&y.push({accountId:S.id,outputs:A.data.outputs})):c.push({accountId:S.id,error:A.error.message})}),t.onCascadePhaseComplete?.("accounts")}if(t.onCascadeComplete?.({platformDeployed:k,domainsDeployed:F,accountsDeployed:l,accountsFailed:c.length,errors:c}),c.length>0){const i=c.map(N=>` ${N.accountId}: ${N.error}`).join(`
|
|
2
|
+
`);t.onLog?.(E(`Cascade completed with ${c.length} failure(s):
|
|
3
|
+
${i}`),"warn")}}const X=c.length>0?c.map(l=>E(`${l.accountId}: ${l.error}`)):void 0;return K({target:a.target,deploymentType:"organisation",outputs:Q,...y.length>0?{cascadeOutputs:y}:{},durationMs:Date.now()-s,warnings:X})}export{ft as deployOrganisation};
|