@devramps/cli 0.1.2 → 0.1.4
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/index.js +240 -147
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -74,80 +74,178 @@ var CloudFormationError = class extends DevRampsError {
|
|
|
74
74
|
// src/utils/logger.ts
|
|
75
75
|
import chalk from "chalk";
|
|
76
76
|
var verboseMode = false;
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
barWidth = 30;
|
|
77
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
78
|
+
var MultiStackProgress = class {
|
|
79
|
+
stacks = /* @__PURE__ */ new Map();
|
|
80
|
+
stackOrder = [];
|
|
82
81
|
lastLineCount = 0;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
isTTY;
|
|
83
|
+
spinnerFrame = 0;
|
|
84
|
+
spinnerInterval = null;
|
|
85
|
+
barWidth = 20;
|
|
86
|
+
constructor() {
|
|
87
|
+
this.isTTY = process.stdout.isTTY ?? false;
|
|
88
|
+
if (this.isTTY) {
|
|
89
|
+
this.spinnerInterval = setInterval(() => {
|
|
90
|
+
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
91
|
+
this.render();
|
|
92
|
+
}, 80);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register a stack to track
|
|
97
|
+
*/
|
|
98
|
+
addStack(stackName, accountId, region, totalResources) {
|
|
99
|
+
this.stacks.set(stackName, {
|
|
100
|
+
stackName,
|
|
101
|
+
accountId,
|
|
102
|
+
region,
|
|
103
|
+
completed: 0,
|
|
104
|
+
total: totalResources,
|
|
105
|
+
status: "pending"
|
|
106
|
+
});
|
|
107
|
+
this.stackOrder.push(stackName);
|
|
88
108
|
this.render();
|
|
89
109
|
}
|
|
90
110
|
/**
|
|
91
|
-
* Update
|
|
111
|
+
* Update a stack's progress
|
|
92
112
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
113
|
+
updateStack(stackName, completed, status, latestEvent, latestResourceId) {
|
|
114
|
+
const stack = this.stacks.get(stackName);
|
|
115
|
+
if (stack) {
|
|
116
|
+
stack.completed = completed;
|
|
117
|
+
stack.status = status;
|
|
118
|
+
if (latestEvent) stack.latestEvent = latestEvent;
|
|
119
|
+
if (latestResourceId) stack.latestResourceId = latestResourceId;
|
|
120
|
+
this.render();
|
|
100
121
|
}
|
|
101
|
-
this.render();
|
|
102
122
|
}
|
|
103
123
|
/**
|
|
104
|
-
*
|
|
124
|
+
* Mark a stack as started
|
|
105
125
|
*/
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
if (
|
|
109
|
-
|
|
126
|
+
startStack(stackName) {
|
|
127
|
+
const stack = this.stacks.get(stackName);
|
|
128
|
+
if (stack) {
|
|
129
|
+
stack.status = "in_progress";
|
|
130
|
+
this.render();
|
|
110
131
|
}
|
|
111
|
-
this.render();
|
|
112
132
|
}
|
|
113
133
|
/**
|
|
114
|
-
*
|
|
134
|
+
* Mark a stack as complete
|
|
135
|
+
*/
|
|
136
|
+
completeStack(stackName, success2) {
|
|
137
|
+
const stack = this.stacks.get(stackName);
|
|
138
|
+
if (stack) {
|
|
139
|
+
stack.status = success2 ? "complete" : "failed";
|
|
140
|
+
stack.completed = success2 ? stack.total : stack.completed;
|
|
141
|
+
this.render();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Clear the display
|
|
115
146
|
*/
|
|
116
147
|
clear() {
|
|
148
|
+
if (!this.isTTY) return;
|
|
117
149
|
for (let i = 0; i < this.lastLineCount; i++) {
|
|
118
150
|
process.stdout.write("\x1B[A\x1B[2K");
|
|
119
151
|
}
|
|
120
152
|
this.lastLineCount = 0;
|
|
121
153
|
}
|
|
122
154
|
/**
|
|
123
|
-
* Finish and
|
|
155
|
+
* Finish and stop updates
|
|
124
156
|
*/
|
|
125
157
|
finish() {
|
|
158
|
+
if (this.spinnerInterval) {
|
|
159
|
+
clearInterval(this.spinnerInterval);
|
|
160
|
+
this.spinnerInterval = null;
|
|
161
|
+
}
|
|
126
162
|
this.clear();
|
|
163
|
+
this.renderFinal();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Render final state (no clearing, just print)
|
|
167
|
+
*/
|
|
168
|
+
renderFinal() {
|
|
169
|
+
for (const stackName of this.stackOrder) {
|
|
170
|
+
const stack = this.stacks.get(stackName);
|
|
171
|
+
if (!stack) continue;
|
|
172
|
+
console.log(this.formatStackLine(stack, false));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Format a single stack line
|
|
177
|
+
*/
|
|
178
|
+
formatStackLine(stack, withSpinner) {
|
|
179
|
+
const { accountId, region, stackName, completed, total, status, latestEvent, latestResourceId } = stack;
|
|
180
|
+
let statusIndicator;
|
|
181
|
+
let colorFn;
|
|
182
|
+
switch (status) {
|
|
183
|
+
case "complete":
|
|
184
|
+
statusIndicator = "\u2714";
|
|
185
|
+
colorFn = chalk.green;
|
|
186
|
+
break;
|
|
187
|
+
case "failed":
|
|
188
|
+
case "rollback":
|
|
189
|
+
statusIndicator = "\u2716";
|
|
190
|
+
colorFn = chalk.red;
|
|
191
|
+
break;
|
|
192
|
+
case "in_progress":
|
|
193
|
+
statusIndicator = withSpinner ? SPINNER_FRAMES[this.spinnerFrame] : "\u22EF";
|
|
194
|
+
colorFn = chalk.blue;
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
statusIndicator = "\u25CB";
|
|
198
|
+
colorFn = chalk.gray;
|
|
199
|
+
}
|
|
200
|
+
const percentage = total > 0 ? completed / total : 0;
|
|
201
|
+
const filled = Math.round(this.barWidth * percentage);
|
|
202
|
+
const empty = this.barWidth - filled;
|
|
203
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
204
|
+
const accountLabel = `[${accountId} - ${region}]`;
|
|
205
|
+
const countLabel = `(${completed}/${total})`;
|
|
206
|
+
let eventLabel = "";
|
|
207
|
+
if (latestEvent && latestResourceId) {
|
|
208
|
+
const maxEventLen = 30;
|
|
209
|
+
const resourceIdTrunc = latestResourceId.length > 20 ? latestResourceId.slice(0, 17) + "..." : latestResourceId;
|
|
210
|
+
eventLabel = ` ${latestEvent} ${resourceIdTrunc}`;
|
|
211
|
+
if (eventLabel.length > maxEventLen) {
|
|
212
|
+
eventLabel = eventLabel.slice(0, maxEventLen - 3) + "...";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const line = `${statusIndicator} ${accountLabel} ${stackName} [${bar}] ${countLabel}${eventLabel}`;
|
|
216
|
+
return colorFn(line);
|
|
127
217
|
}
|
|
128
218
|
/**
|
|
129
|
-
* Render
|
|
219
|
+
* Render all stack progress bars
|
|
130
220
|
*/
|
|
131
221
|
render() {
|
|
222
|
+
if (!this.isTTY) return;
|
|
132
223
|
this.clear();
|
|
133
224
|
const lines = [];
|
|
134
|
-
for (const
|
|
135
|
-
|
|
225
|
+
for (const stackName of this.stackOrder) {
|
|
226
|
+
const stack = this.stacks.get(stackName);
|
|
227
|
+
if (!stack) continue;
|
|
228
|
+
lines.push(this.formatStackLine(stack, true));
|
|
136
229
|
}
|
|
137
|
-
const percentage = this.total > 0 ? this.current / this.total : 0;
|
|
138
|
-
const filled = Math.round(this.barWidth * percentage);
|
|
139
|
-
const empty = this.barWidth - filled;
|
|
140
|
-
const bar = chalk.green("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
|
|
141
|
-
const count = chalk.cyan(`${this.current}/${this.total}`);
|
|
142
|
-
const labelText = chalk.bold(this.label);
|
|
143
|
-
lines.push(`${labelText} ${bar} ${count} resources`);
|
|
144
|
-
lines.push("");
|
|
145
230
|
for (const line of lines) {
|
|
146
231
|
process.stdout.write(line + "\n");
|
|
147
232
|
}
|
|
148
233
|
this.lastLineCount = lines.length;
|
|
149
234
|
}
|
|
150
235
|
};
|
|
236
|
+
var globalProgress = null;
|
|
237
|
+
function getMultiStackProgress() {
|
|
238
|
+
if (!globalProgress) {
|
|
239
|
+
globalProgress = new MultiStackProgress();
|
|
240
|
+
}
|
|
241
|
+
return globalProgress;
|
|
242
|
+
}
|
|
243
|
+
function clearMultiStackProgress() {
|
|
244
|
+
if (globalProgress) {
|
|
245
|
+
globalProgress.finish();
|
|
246
|
+
globalProgress = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
151
249
|
function setVerbose(enabled) {
|
|
152
250
|
verboseMode = enabled;
|
|
153
251
|
}
|
|
@@ -353,7 +451,7 @@ async function previewStackChanges(options) {
|
|
|
353
451
|
const stackStatus = await getStackStatus(stackName, credentials, region);
|
|
354
452
|
const changeSetName = `devramps-preview-${Date.now()}`;
|
|
355
453
|
if (!stackStatus.exists) {
|
|
356
|
-
info(` Stack ${stackName} will be created (new stack)`);
|
|
454
|
+
info(` Stack ${stackName} will be created (new stack) in account ${options.accountId} (${region || "default region"})`);
|
|
357
455
|
return;
|
|
358
456
|
}
|
|
359
457
|
try {
|
|
@@ -376,7 +474,7 @@ async function previewStackChanges(options) {
|
|
|
376
474
|
ChangeSetName: changeSetName
|
|
377
475
|
})
|
|
378
476
|
);
|
|
379
|
-
logStackChanges(stackName, changeSetResponse.Changes || [], stackStatus.exists);
|
|
477
|
+
logStackChanges(stackName, changeSetResponse.Changes || [], stackStatus.exists, options.accountId, region);
|
|
380
478
|
await client.send(
|
|
381
479
|
new DeleteChangeSetCommand({
|
|
382
480
|
StackName: stackName,
|
|
@@ -401,13 +499,13 @@ async function previewStackChanges(options) {
|
|
|
401
499
|
verbose(` Could not preview changes for ${stackName}: ${errorMessage}`);
|
|
402
500
|
}
|
|
403
501
|
}
|
|
404
|
-
function logStackChanges(stackName, changes, isUpdate) {
|
|
502
|
+
function logStackChanges(stackName, changes, isUpdate, accountId, region) {
|
|
405
503
|
if (changes.length === 0) {
|
|
406
504
|
verbose(` Stack ${stackName}: No changes`);
|
|
407
505
|
return;
|
|
408
506
|
}
|
|
409
507
|
const action = isUpdate ? "update" : "create";
|
|
410
|
-
info(` Stack ${stackName} will ${action} ${changes.length} resource(s):`);
|
|
508
|
+
info(` Stack ${stackName} will ${action} ${changes.length} resource(s) in account ${accountId} (${region || "default region"}):`);
|
|
411
509
|
for (const change of changes) {
|
|
412
510
|
const resourceChange = change.ResourceChange;
|
|
413
511
|
if (!resourceChange) continue;
|
|
@@ -450,31 +548,18 @@ var SUCCESS_STATES = /* @__PURE__ */ new Set([
|
|
|
450
548
|
"CREATE_COMPLETE",
|
|
451
549
|
"UPDATE_COMPLETE"
|
|
452
550
|
]);
|
|
453
|
-
function getStatusSymbol(status) {
|
|
454
|
-
if (!status) return "?";
|
|
455
|
-
if (status.includes("COMPLETE") && !status.includes("ROLLBACK")) return "\u2714";
|
|
456
|
-
if (status.includes("FAILED") || status.includes("ROLLBACK")) return "\u2716";
|
|
457
|
-
if (status.includes("IN_PROGRESS")) return "\u22EF";
|
|
458
|
-
return "?";
|
|
459
|
-
}
|
|
460
|
-
function formatStackEvent(event) {
|
|
461
|
-
const symbol = getStatusSymbol(event.ResourceStatus);
|
|
462
|
-
const resourceType = event.ResourceType || "Unknown";
|
|
463
|
-
const logicalId = event.LogicalResourceId || "Unknown";
|
|
464
|
-
const status = event.ResourceStatus || "Unknown";
|
|
465
|
-
const reason = event.ResourceStatusReason ? ` - ${event.ResourceStatusReason}` : "";
|
|
466
|
-
return ` ${symbol} ${resourceType} (${logicalId}): ${status}${reason}`;
|
|
467
|
-
}
|
|
468
551
|
function isResourceComplete(status) {
|
|
469
552
|
if (!status) return false;
|
|
470
553
|
return status.includes("_COMPLETE") && !status.includes("ROLLBACK");
|
|
471
554
|
}
|
|
472
|
-
async function waitForStackWithProgress(client, stackName, operationStartTime, totalResources, maxWaitTime = 600
|
|
555
|
+
async function waitForStackWithProgress(client, stackName, operationStartTime, totalResources, maxWaitTime = 600) {
|
|
473
556
|
const seenEventIds = /* @__PURE__ */ new Set();
|
|
474
557
|
const completedResources = /* @__PURE__ */ new Set();
|
|
475
558
|
const startTime = Date.now();
|
|
476
559
|
const pollInterval = 3e3;
|
|
477
|
-
const
|
|
560
|
+
const progress = getMultiStackProgress();
|
|
561
|
+
let latestEvent = "";
|
|
562
|
+
let latestResourceId = "";
|
|
478
563
|
try {
|
|
479
564
|
while (true) {
|
|
480
565
|
if (Date.now() - startTime > maxWaitTime * 1e3) {
|
|
@@ -500,19 +585,27 @@ async function waitForStackWithProgress(client, stackName, operationStartTime, t
|
|
|
500
585
|
seenEventIds.add(event.EventId);
|
|
501
586
|
}
|
|
502
587
|
const logicalId = event.LogicalResourceId;
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
588
|
+
const status = event.ResourceStatus || "";
|
|
589
|
+
if (logicalId && logicalId !== stackName) {
|
|
590
|
+
latestEvent = status;
|
|
591
|
+
latestResourceId = logicalId;
|
|
592
|
+
if (isResourceComplete(status)) {
|
|
593
|
+
completedResources.add(logicalId);
|
|
594
|
+
}
|
|
508
595
|
}
|
|
509
596
|
}
|
|
510
597
|
const currentStatus = stack.StackStatus || "";
|
|
598
|
+
let displayStatus = "in_progress";
|
|
599
|
+
if (currentStatus.includes("ROLLBACK")) {
|
|
600
|
+
displayStatus = "rollback";
|
|
601
|
+
} else if (currentStatus.includes("FAILED")) {
|
|
602
|
+
displayStatus = "failed";
|
|
603
|
+
}
|
|
604
|
+
progress.updateStack(stackName, completedResources.size, displayStatus, latestEvent, latestResourceId);
|
|
511
605
|
if (TERMINAL_STATES.has(currentStatus)) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (SUCCESS_STATES.has(currentStatus)) {
|
|
606
|
+
const success2 = SUCCESS_STATES.has(currentStatus);
|
|
607
|
+
progress.completeStack(stackName, success2);
|
|
608
|
+
if (success2) {
|
|
516
609
|
return;
|
|
517
610
|
}
|
|
518
611
|
throw new Error(`Stack operation failed with status: ${currentStatus}`);
|
|
@@ -520,39 +613,41 @@ async function waitForStackWithProgress(client, stackName, operationStartTime, t
|
|
|
520
613
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
521
614
|
}
|
|
522
615
|
} catch (error2) {
|
|
523
|
-
|
|
524
|
-
progressBar.finish();
|
|
525
|
-
}
|
|
616
|
+
progress.completeStack(stackName, false);
|
|
526
617
|
throw error2;
|
|
527
618
|
}
|
|
528
619
|
}
|
|
529
620
|
async function deployStack(options) {
|
|
530
|
-
const { stackName, template, accountId, region, credentials
|
|
621
|
+
const { stackName, template, accountId, region, credentials } = options;
|
|
531
622
|
const client = new CloudFormationClient({
|
|
532
623
|
credentials,
|
|
533
624
|
region
|
|
534
625
|
});
|
|
535
626
|
const templateBody = JSON.stringify(template);
|
|
536
627
|
const resourceCount = Object.keys(template.Resources || {}).length;
|
|
628
|
+
const progress = getMultiStackProgress();
|
|
629
|
+
progress.startStack(stackName);
|
|
537
630
|
try {
|
|
538
631
|
const stackStatus = await getStackStatus(stackName, credentials, region);
|
|
539
632
|
if (stackStatus.exists) {
|
|
540
633
|
verbose(`Stack ${stackName} exists, updating...`);
|
|
541
|
-
await updateStack(client, stackName, templateBody, accountId, resourceCount
|
|
634
|
+
await updateStack(client, stackName, templateBody, accountId, resourceCount);
|
|
542
635
|
} else {
|
|
543
636
|
verbose(`Stack ${stackName} does not exist, creating...`);
|
|
544
|
-
await createStack(client, stackName, templateBody, accountId, resourceCount
|
|
637
|
+
await createStack(client, stackName, templateBody, accountId, resourceCount);
|
|
545
638
|
}
|
|
546
639
|
} catch (error2) {
|
|
547
640
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
548
641
|
if (errorMessage.includes("No updates are to be performed")) {
|
|
549
642
|
verbose(`Stack ${stackName} is already up to date`);
|
|
643
|
+
progress.completeStack(stackName, true);
|
|
550
644
|
return;
|
|
551
645
|
}
|
|
646
|
+
progress.completeStack(stackName, false);
|
|
552
647
|
throw new CloudFormationError(stackName, accountId, errorMessage);
|
|
553
648
|
}
|
|
554
649
|
}
|
|
555
|
-
async function createStack(client, stackName, templateBody,
|
|
650
|
+
async function createStack(client, stackName, templateBody, _accountId, resourceCount) {
|
|
556
651
|
const operationStartTime = /* @__PURE__ */ new Date();
|
|
557
652
|
await client.send(
|
|
558
653
|
new CreateStackCommand({
|
|
@@ -565,11 +660,9 @@ async function createStack(client, stackName, templateBody, accountId, resourceC
|
|
|
565
660
|
]
|
|
566
661
|
})
|
|
567
662
|
);
|
|
568
|
-
|
|
569
|
-
await waitForStackWithProgress(client, stackName, operationStartTime, resourceCount, 600, showProgress);
|
|
570
|
-
success(`Stack ${stackName} created successfully in account ${accountId}`);
|
|
663
|
+
await waitForStackWithProgress(client, stackName, operationStartTime, resourceCount);
|
|
571
664
|
}
|
|
572
|
-
async function updateStack(client, stackName, templateBody,
|
|
665
|
+
async function updateStack(client, stackName, templateBody, _accountId, resourceCount) {
|
|
573
666
|
const operationStartTime = /* @__PURE__ */ new Date();
|
|
574
667
|
await client.send(
|
|
575
668
|
new UpdateStackCommand({
|
|
@@ -578,9 +671,7 @@ async function updateStack(client, stackName, templateBody, accountId, resourceC
|
|
|
578
671
|
Capabilities: ["CAPABILITY_NAMED_IAM"]
|
|
579
672
|
})
|
|
580
673
|
);
|
|
581
|
-
|
|
582
|
-
await waitForStackWithProgress(client, stackName, operationStartTime, resourceCount, 600, showProgress);
|
|
583
|
-
success(`Stack ${stackName} updated successfully in account ${accountId}`);
|
|
674
|
+
await waitForStackWithProgress(client, stackName, operationStartTime, resourceCount);
|
|
584
675
|
}
|
|
585
676
|
async function readExistingStack(stackName, accountId, region, credentials) {
|
|
586
677
|
const client = new CloudFormationClient({
|
|
@@ -2548,30 +2639,39 @@ async function showDryRunPlan(plan) {
|
|
|
2548
2639
|
info(`CI/CD Account: ${plan.cicdAccountId}`);
|
|
2549
2640
|
info(`CI/CD Region: ${plan.cicdRegion}`);
|
|
2550
2641
|
newline();
|
|
2551
|
-
info("
|
|
2642
|
+
info("Org Stack:");
|
|
2552
2643
|
info(` ${plan.orgStack.action}: ${plan.orgStack.stackName}`);
|
|
2553
|
-
info(` Account: ${plan.orgStack.accountId}`);
|
|
2644
|
+
info(` Account: ${plan.orgStack.accountId}, Region: ${plan.orgStack.region}`);
|
|
2554
2645
|
info(` Target accounts with bucket access: ${plan.orgStack.targetAccountIds.length}`);
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2646
|
+
if (plan.pipelineStacks.length > 0) {
|
|
2647
|
+
newline();
|
|
2648
|
+
info("Pipeline Stacks:");
|
|
2649
|
+
for (const stack of plan.pipelineStacks) {
|
|
2650
|
+
info(` ${stack.action}: ${stack.stackName}`);
|
|
2651
|
+
info(` Account: ${stack.accountId}, Region: ${stack.region}`);
|
|
2652
|
+
info(` ECR repos: ${stack.dockerArtifacts.length}, S3 buckets: ${stack.bundleArtifacts.length}`);
|
|
2653
|
+
}
|
|
2560
2654
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
info(
|
|
2655
|
+
if (plan.accountStacks.length > 0) {
|
|
2656
|
+
newline();
|
|
2657
|
+
info("Account Stacks:");
|
|
2658
|
+
for (const stack of plan.accountStacks) {
|
|
2659
|
+
info(` ${stack.action}: ${stack.stackName}`);
|
|
2660
|
+
info(` Account: ${stack.accountId}, Region: ${stack.region} (OIDC provider)`);
|
|
2661
|
+
}
|
|
2564
2662
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2663
|
+
if (plan.stageStacks.length > 0) {
|
|
2664
|
+
newline();
|
|
2665
|
+
info("Stage Stacks:");
|
|
2666
|
+
for (const stack of plan.stageStacks) {
|
|
2667
|
+
info(` ${stack.action}: ${stack.stackName}`);
|
|
2668
|
+
info(` Account: ${stack.accountId}, Region: ${stack.region}`);
|
|
2669
|
+
info(` ECR repos: ${stack.dockerArtifacts.length}, S3 buckets: ${stack.bundleArtifacts.length}`);
|
|
2670
|
+
}
|
|
2571
2671
|
}
|
|
2572
2672
|
const totalStacks = 1 + plan.pipelineStacks.length + plan.accountStacks.length + plan.stageStacks.length;
|
|
2573
2673
|
newline();
|
|
2574
|
-
info(`Total stacks to deploy: ${totalStacks}`);
|
|
2674
|
+
info(`Total stacks to deploy (in parallel): ${totalStacks}`);
|
|
2575
2675
|
}
|
|
2576
2676
|
async function confirmDeploymentPlan(plan) {
|
|
2577
2677
|
const totalStacks = 1 + plan.pipelineStacks.length + plan.accountStacks.length + plan.stageStacks.length;
|
|
@@ -2593,22 +2693,36 @@ async function confirmDeploymentPlan(plan) {
|
|
|
2593
2693
|
}
|
|
2594
2694
|
async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, currentAccountId, options) {
|
|
2595
2695
|
const results = { success: 0, failed: 0 };
|
|
2696
|
+
const totalStacks = 1 + plan.pipelineStacks.length + plan.accountStacks.length + plan.stageStacks.length;
|
|
2596
2697
|
newline();
|
|
2597
|
-
header("
|
|
2598
|
-
|
|
2599
|
-
await deployOrgStack(plan, pipelines, authData, currentAccountId, options);
|
|
2600
|
-
results.success++;
|
|
2601
|
-
success("Org stack deployed successfully");
|
|
2602
|
-
} catch (error2) {
|
|
2603
|
-
results.failed++;
|
|
2604
|
-
error(`Org stack failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
2605
|
-
throw error2;
|
|
2606
|
-
}
|
|
2607
|
-
newline();
|
|
2608
|
-
header("Phase 2: Pipeline & Account Stacks (parallel)");
|
|
2609
|
-
const totalPhase2Stacks = plan.pipelineStacks.length + plan.accountStacks.length;
|
|
2610
|
-
info(`Deploying ${totalPhase2Stacks} stack(s) in parallel...`);
|
|
2698
|
+
header("Deploying All Stacks");
|
|
2699
|
+
info(`Deploying ${totalStacks} stack(s) in parallel...`);
|
|
2611
2700
|
newline();
|
|
2701
|
+
const progress = getMultiStackProgress();
|
|
2702
|
+
progress.addStack(plan.orgStack.stackName, plan.orgStack.accountId, plan.orgStack.region, 5);
|
|
2703
|
+
for (const stack of plan.pipelineStacks) {
|
|
2704
|
+
const resourceCount = stack.dockerArtifacts.length + stack.bundleArtifacts.length;
|
|
2705
|
+
progress.addStack(stack.stackName, stack.accountId, stack.region, Math.max(resourceCount, 1));
|
|
2706
|
+
}
|
|
2707
|
+
for (const stack of plan.accountStacks) {
|
|
2708
|
+
progress.addStack(stack.stackName, stack.accountId, stack.region, 1);
|
|
2709
|
+
}
|
|
2710
|
+
for (const stack of plan.stageStacks) {
|
|
2711
|
+
const resourceCount = stack.dockerArtifacts.length + stack.bundleArtifacts.length + 2;
|
|
2712
|
+
progress.addStack(stack.stackName, stack.accountId, stack.region, resourceCount);
|
|
2713
|
+
}
|
|
2714
|
+
const orgPromise = (async () => {
|
|
2715
|
+
try {
|
|
2716
|
+
await deployOrgStack(plan, pipelines, authData, currentAccountId, options);
|
|
2717
|
+
return { stack: plan.orgStack.stackName, success: true };
|
|
2718
|
+
} catch (error2) {
|
|
2719
|
+
return {
|
|
2720
|
+
stack: plan.orgStack.stackName,
|
|
2721
|
+
success: false,
|
|
2722
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
})();
|
|
2612
2726
|
const pipelinePromises = plan.pipelineStacks.map(async (stack) => {
|
|
2613
2727
|
try {
|
|
2614
2728
|
await deployPipelineStack(stack, authData, currentAccountId, options);
|
|
@@ -2624,36 +2738,15 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
|
|
|
2624
2738
|
const accountPromises = plan.accountStacks.map(async (stack) => {
|
|
2625
2739
|
try {
|
|
2626
2740
|
await deployAccountStack(stack, currentAccountId, options);
|
|
2627
|
-
return { stack:
|
|
2741
|
+
return { stack: stack.stackName, success: true };
|
|
2628
2742
|
} catch (error2) {
|
|
2629
2743
|
return {
|
|
2630
|
-
stack:
|
|
2744
|
+
stack: stack.stackName,
|
|
2631
2745
|
success: false,
|
|
2632
2746
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
2633
2747
|
};
|
|
2634
2748
|
}
|
|
2635
2749
|
});
|
|
2636
|
-
const pipelineResults = await Promise.all(pipelinePromises);
|
|
2637
|
-
const accountResults = await Promise.all(accountPromises);
|
|
2638
|
-
newline();
|
|
2639
|
-
let accountStacksFailed = false;
|
|
2640
|
-
for (const result of [...pipelineResults, ...accountResults]) {
|
|
2641
|
-
if (result.success) {
|
|
2642
|
-
success(`${result.stack} deployed`);
|
|
2643
|
-
results.success++;
|
|
2644
|
-
} else {
|
|
2645
|
-
error(`${result.stack} failed: ${result.error}`);
|
|
2646
|
-
results.failed++;
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
accountStacksFailed = accountResults.some((r) => !r.success);
|
|
2650
|
-
if (accountStacksFailed) {
|
|
2651
|
-
warn("Some Account stacks failed. Stage stacks may fail if their account OIDC provider did not deploy.");
|
|
2652
|
-
}
|
|
2653
|
-
newline();
|
|
2654
|
-
header("Phase 3: Stage Stacks (parallel)");
|
|
2655
|
-
info(`Deploying ${plan.stageStacks.length} stack(s) in parallel...`);
|
|
2656
|
-
newline();
|
|
2657
2750
|
const stagePromises = plan.stageStacks.map(async (stack) => {
|
|
2658
2751
|
try {
|
|
2659
2752
|
await deployStageStack(stack, authData, currentAccountId, options);
|
|
@@ -2666,9 +2759,15 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
|
|
|
2666
2759
|
};
|
|
2667
2760
|
}
|
|
2668
2761
|
});
|
|
2669
|
-
const
|
|
2762
|
+
const allResults = await Promise.all([
|
|
2763
|
+
orgPromise,
|
|
2764
|
+
...pipelinePromises,
|
|
2765
|
+
...accountPromises,
|
|
2766
|
+
...stagePromises
|
|
2767
|
+
]);
|
|
2768
|
+
clearMultiStackProgress();
|
|
2670
2769
|
newline();
|
|
2671
|
-
for (const result of
|
|
2770
|
+
for (const result of allResults) {
|
|
2672
2771
|
if (result.success) {
|
|
2673
2772
|
success(`${result.stack} deployed`);
|
|
2674
2773
|
results.success++;
|
|
@@ -2745,9 +2844,7 @@ async function deployPipelineStack(stack, authData, currentAccountId, options) {
|
|
|
2745
2844
|
template,
|
|
2746
2845
|
accountId: cicdAccountId,
|
|
2747
2846
|
region: cicdRegion,
|
|
2748
|
-
credentials
|
|
2749
|
-
showProgress: false
|
|
2750
|
-
// Disable progress bar for parallel deployment
|
|
2847
|
+
credentials
|
|
2751
2848
|
};
|
|
2752
2849
|
await previewStackChanges(deployOptions);
|
|
2753
2850
|
await deployStack(deployOptions);
|
|
@@ -2764,9 +2861,7 @@ async function deployAccountStack(stack, currentAccountId, options) {
|
|
|
2764
2861
|
template,
|
|
2765
2862
|
accountId: stack.accountId,
|
|
2766
2863
|
region: stack.region,
|
|
2767
|
-
credentials
|
|
2768
|
-
showProgress: false
|
|
2769
|
-
// Disable progress bar for parallel deployment
|
|
2864
|
+
credentials
|
|
2770
2865
|
};
|
|
2771
2866
|
await previewStackChanges(deployOptions);
|
|
2772
2867
|
await deployStack(deployOptions);
|
|
@@ -2792,9 +2887,7 @@ async function deployStageStack(stack, authData, currentAccountId, options) {
|
|
|
2792
2887
|
template,
|
|
2793
2888
|
accountId: stack.accountId,
|
|
2794
2889
|
region: stack.region,
|
|
2795
|
-
credentials
|
|
2796
|
-
showProgress: false
|
|
2797
|
-
// Disable progress bar for parallel deployment
|
|
2890
|
+
credentials
|
|
2798
2891
|
};
|
|
2799
2892
|
await previewStackChanges(deployOptions);
|
|
2800
2893
|
await deployStack(deployOptions);
|