@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.
Files changed (2) hide show
  1. package/dist/index.js +240 -147
  2. 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 ProgressBar = class {
78
- current = 0;
79
- total = 0;
80
- label;
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
- eventLines = [];
84
- maxVisibleEvents = 5;
85
- constructor(label, total) {
86
- this.label = label;
87
- this.total = total;
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 progress and re-render
111
+ * Update a stack's progress
92
112
  */
93
- update(current, eventMessage) {
94
- this.current = current;
95
- if (eventMessage) {
96
- this.eventLines.push(eventMessage);
97
- if (this.eventLines.length > this.maxVisibleEvents) {
98
- this.eventLines.shift();
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
- * Add an event message without changing progress
124
+ * Mark a stack as started
105
125
  */
106
- addEvent(message) {
107
- this.eventLines.push(message);
108
- if (this.eventLines.length > this.maxVisibleEvents) {
109
- this.eventLines.shift();
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
- * Clear the progress bar from the terminal
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 clear the progress bar
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 the progress bar
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 event of this.eventLines) {
135
- lines.push(event);
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, showProgress = true) {
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 progressBar = showProgress ? new ProgressBar(stackName, totalResources) : null;
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
- if (logicalId && logicalId !== stackName && isResourceComplete(event.ResourceStatus)) {
504
- completedResources.add(logicalId);
505
- }
506
- if (progressBar) {
507
- progressBar.update(completedResources.size, formatStackEvent(event));
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
- if (progressBar) {
513
- progressBar.finish();
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
- if (progressBar) {
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, showProgress = true } = options;
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, showProgress);
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, showProgress);
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, accountId, resourceCount, showProgress = true) {
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
- info(`Creating stack ${stackName}...`);
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, accountId, resourceCount, showProgress = true) {
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
- info(`Updating stack ${stackName}...`);
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("Phase 1: Org Stack");
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
- newline();
2556
- info("Phase 2: Pipeline & Account Stacks (parallel)");
2557
- for (const stack of plan.pipelineStacks) {
2558
- info(` ${stack.action}: ${stack.stackName}`);
2559
- info(` ECR repos: ${stack.dockerArtifacts.length}, S3 buckets: ${stack.bundleArtifacts.length}`);
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
- for (const stack of plan.accountStacks) {
2562
- info(` ${stack.action}: ${stack.stackName}`);
2563
- info(` Account: ${stack.accountId} (OIDC provider)`);
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
- newline();
2566
- info("Phase 3: Stage Stacks (parallel)");
2567
- for (const stack of plan.stageStacks) {
2568
- info(` ${stack.action}: ${stack.stackName}`);
2569
- info(` Account: ${stack.accountId}, Region: ${stack.region}`);
2570
- info(` ECR repos: ${stack.dockerArtifacts.length}, S3 buckets: ${stack.bundleArtifacts.length}`);
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("Phase 1: Org Stack");
2598
- try {
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: `${stack.stackName} (${stack.accountId})`, success: true };
2741
+ return { stack: stack.stackName, success: true };
2628
2742
  } catch (error2) {
2629
2743
  return {
2630
- stack: `${stack.stackName} (${stack.accountId})`,
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 phase3Results = await Promise.all(stagePromises);
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 phase3Results) {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devramps/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "DevRamps CLI - Bootstrap AWS infrastructure for CI/CD pipelines",
5
5
  "main": "dist/index.js",
6
6
  "bin": {