@devramps/cli 0.1.6 → 0.1.7

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 +89 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -81,18 +81,34 @@ function getStackKey(stackName, accountId, region) {
81
81
  var MultiStackProgress = class {
82
82
  stacks = /* @__PURE__ */ new Map();
83
83
  stackOrder = [];
84
- lastLineCount = 0;
85
84
  isTTY;
86
85
  spinnerFrame = 0;
87
86
  spinnerInterval = null;
88
87
  barWidth = 20;
88
+ renderScheduled = false;
89
+ lastRenderTime = 0;
90
+ hasRenderedOnce = false;
91
+ useAltScreen = true;
92
+ // Use alternate screen buffer for clean display
89
93
  constructor() {
90
94
  this.isTTY = process.stdout.isTTY ?? false;
95
+ }
96
+ /**
97
+ * Start the progress display (call after all stacks are registered)
98
+ */
99
+ start() {
91
100
  if (this.isTTY) {
101
+ if (this.useAltScreen) {
102
+ process.stdout.write("\x1B[?1049h");
103
+ }
104
+ process.stdout.write("\x1B[?25l");
105
+ process.stdout.write("\x1B[H");
106
+ process.stdout.write("\x1B[2J");
107
+ this.doRender();
92
108
  this.spinnerInterval = setInterval(() => {
93
109
  this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
94
- this.render();
95
- }, 80);
110
+ this.scheduleRender();
111
+ }, 100);
96
112
  }
97
113
  }
98
114
  /**
@@ -112,20 +128,19 @@ var MultiStackProgress = class {
112
128
  status: "pending"
113
129
  });
114
130
  this.stackOrder.push(key);
115
- this.render();
116
131
  }
117
132
  /**
118
133
  * Update a stack's progress
119
134
  */
120
- updateStack(stackName, accountId, region, completed, status, latestEvent, latestResourceId) {
135
+ updateStack(stackName, accountId, region, completed, status, cfnStatus, latestResourceId) {
121
136
  const key = getStackKey(stackName, accountId, region);
122
137
  const stack = this.stacks.get(key);
123
138
  if (stack) {
124
139
  stack.completed = completed;
125
140
  stack.status = status;
126
- if (latestEvent) stack.latestEvent = latestEvent;
141
+ if (cfnStatus) stack.cfnStatus = cfnStatus;
127
142
  if (latestResourceId) stack.latestResourceId = latestResourceId;
128
- this.render();
143
+ this.scheduleRender();
129
144
  }
130
145
  }
131
146
  /**
@@ -136,30 +151,40 @@ var MultiStackProgress = class {
136
151
  const stack = this.stacks.get(key);
137
152
  if (stack) {
138
153
  stack.status = "in_progress";
139
- this.render();
154
+ stack.cfnStatus = "STARTING";
155
+ this.scheduleRender();
140
156
  }
141
157
  }
142
158
  /**
143
159
  * Mark a stack as complete
144
160
  */
145
- completeStack(stackName, accountId, region, success2) {
161
+ completeStack(stackName, accountId, region, success2, failureReason) {
146
162
  const key = getStackKey(stackName, accountId, region);
147
163
  const stack = this.stacks.get(key);
148
164
  if (stack) {
149
165
  stack.status = success2 ? "complete" : "failed";
150
166
  stack.completed = success2 ? stack.total : stack.completed;
151
- this.render();
167
+ if (failureReason) stack.failureReason = failureReason;
168
+ this.scheduleRender();
152
169
  }
153
170
  }
154
171
  /**
155
- * Clear the display
172
+ * Schedule a render (debounced to prevent too many updates)
156
173
  */
157
- clear() {
158
- if (!this.isTTY) return;
159
- for (let i = 0; i < this.lastLineCount; i++) {
160
- process.stdout.write("\x1B[A\x1B[2K");
174
+ scheduleRender() {
175
+ if (this.renderScheduled) return;
176
+ const now = Date.now();
177
+ const timeSinceLastRender = now - this.lastRenderTime;
178
+ const minInterval = 50;
179
+ if (timeSinceLastRender >= minInterval) {
180
+ this.doRender();
181
+ } else {
182
+ this.renderScheduled = true;
183
+ setTimeout(() => {
184
+ this.renderScheduled = false;
185
+ this.doRender();
186
+ }, minInterval - timeSinceLastRender);
161
187
  }
162
- this.lastLineCount = 0;
163
188
  }
164
189
  /**
165
190
  * Finish and stop updates
@@ -169,7 +194,12 @@ var MultiStackProgress = class {
169
194
  clearInterval(this.spinnerInterval);
170
195
  this.spinnerInterval = null;
171
196
  }
172
- this.clear();
197
+ if (this.isTTY) {
198
+ process.stdout.write("\x1B[?25h");
199
+ if (this.useAltScreen) {
200
+ process.stdout.write("\x1B[?1049l");
201
+ }
202
+ }
173
203
  this.renderFinal();
174
204
  }
175
205
  /**
@@ -186,7 +216,7 @@ var MultiStackProgress = class {
186
216
  * Format a single stack line
187
217
  */
188
218
  formatStackLine(stack, withSpinner) {
189
- const { accountId, region, stackName, completed, total, status, latestEvent, latestResourceId } = stack;
219
+ const { accountId, region, stackName, completed, total, status, cfnStatus, latestResourceId, failureReason } = stack;
190
220
  let statusIndicator;
191
221
  let colorFn;
192
222
  switch (status) {
@@ -201,7 +231,7 @@ var MultiStackProgress = class {
201
231
  break;
202
232
  case "in_progress":
203
233
  statusIndicator = withSpinner ? SPINNER_FRAMES[this.spinnerFrame] : "\u22EF";
204
- colorFn = chalk.blue;
234
+ colorFn = chalk.cyanBright;
205
235
  break;
206
236
  default:
207
237
  statusIndicator = "\u25CB";
@@ -211,45 +241,47 @@ var MultiStackProgress = class {
211
241
  const filled = Math.round(this.barWidth * percentage);
212
242
  const empty = this.barWidth - filled;
213
243
  const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
214
- const shortAccount = accountId.slice(-4);
215
- const accountLabel = `[...${shortAccount}/${region}]`;
216
- const countLabel = `(${completed}/${total})`;
217
- let eventLabel = "";
218
- if (status === "in_progress" && latestEvent && latestResourceId) {
219
- const shortStatus = latestEvent.replace("CREATE_", "").replace("UPDATE_", "").replace("DELETE_", "");
220
- const resourceIdTrunc = latestResourceId.length > 15 ? latestResourceId.slice(0, 12) + "..." : latestResourceId;
221
- eventLabel = ` ${shortStatus} ${resourceIdTrunc}`;
222
- }
223
- const maxStackNameLen = 40;
224
- const displayName = stackName.length > maxStackNameLen ? "..." + stackName.slice(-(maxStackNameLen - 3)) : stackName;
225
- const line = `${statusIndicator} ${accountLabel} ${displayName} [${bar}] ${countLabel}${eventLabel}`;
244
+ const accountLabel = `${accountId} ${region}`;
245
+ const countLabel = `${completed}/${total}`;
246
+ const maxStackNameLen = 35;
247
+ const displayName = stackName.length > maxStackNameLen ? stackName.slice(0, maxStackNameLen - 3) + "..." : stackName.padEnd(maxStackNameLen);
248
+ let rightInfo = "";
249
+ if (status === "failed" && failureReason) {
250
+ const maxLen = 40;
251
+ rightInfo = failureReason.length > maxLen ? failureReason.slice(0, maxLen - 3) + "..." : failureReason;
252
+ } else if (status === "in_progress") {
253
+ const cfnStatusDisplay = cfnStatus || "DEPLOYING";
254
+ const resourceDisplay = latestResourceId ? latestResourceId.length > 20 ? latestResourceId.slice(0, 17) + "..." : latestResourceId : "";
255
+ rightInfo = resourceDisplay ? `${cfnStatusDisplay} \u2192 ${resourceDisplay}` : cfnStatusDisplay;
256
+ } else if (status === "complete") {
257
+ rightInfo = "COMPLETE";
258
+ }
259
+ const leftPart = `${statusIndicator} ${accountLabel} ${displayName}`;
260
+ const middlePart = `[${bar}] ${countLabel}`;
261
+ const line = `${leftPart} ${middlePart} ${rightInfo}`;
226
262
  return colorFn(line);
227
263
  }
228
264
  /**
229
- * Render all stack progress bars
265
+ * Perform the actual render
230
266
  */
231
- render() {
232
- if (this.isRendering) return;
233
- this.isRendering = true;
234
- try {
235
- if (this.isTTY) {
236
- this.clear();
237
- const lines = [];
238
- for (const key of this.stackOrder) {
239
- const stack = this.stacks.get(key);
240
- if (!stack) continue;
241
- lines.push(this.formatStackLine(stack, true));
242
- }
243
- for (const line of lines) {
244
- process.stdout.write(line + "\n");
245
- }
246
- this.lastLineCount = lines.length;
247
- }
248
- } finally {
249
- this.isRendering = false;
267
+ doRender() {
268
+ this.lastRenderTime = Date.now();
269
+ if (!this.isTTY) return;
270
+ process.stdout.write("\x1B[H");
271
+ process.stdout.write(chalk.bold.underline("Deploying Stacks") + "\x1B[K\n\n");
272
+ for (const key of this.stackOrder) {
273
+ const stack = this.stacks.get(key);
274
+ if (!stack) continue;
275
+ process.stdout.write(this.formatStackLine(stack, true) + "\x1B[K\n");
250
276
  }
277
+ const completed = Array.from(this.stacks.values()).filter((s) => s.status === "complete").length;
278
+ const failed = Array.from(this.stacks.values()).filter((s) => s.status === "failed" || s.status === "rollback").length;
279
+ const inProgress = Array.from(this.stacks.values()).filter((s) => s.status === "in_progress").length;
280
+ const pending = Array.from(this.stacks.values()).filter((s) => s.status === "pending").length;
281
+ process.stdout.write("\n");
282
+ process.stdout.write(chalk.gray(`Progress: ${completed} complete, ${inProgress} in progress, ${pending} pending, ${failed} failed`) + "\x1B[K\n");
283
+ this.hasRenderedOnce = true;
251
284
  }
252
- isRendering = false;
253
285
  };
254
286
  var globalProgress = null;
255
287
  function getMultiStackProgress() {
@@ -572,13 +604,12 @@ function isResourceComplete(status) {
572
604
  if (!status) return false;
573
605
  return status.includes("_COMPLETE") && !status.includes("ROLLBACK");
574
606
  }
575
- async function waitForStackWithProgress(client, stackName, accountId, region, operationStartTime, totalResources, maxWaitTime = 600) {
607
+ async function waitForStackWithProgress(client, stackName, accountId, region, operationStartTime, _totalResources, maxWaitTime = 600) {
576
608
  const seenEventIds = /* @__PURE__ */ new Set();
577
609
  const completedResources = /* @__PURE__ */ new Set();
578
610
  const startTime = Date.now();
579
611
  const pollInterval = 2e3;
580
612
  const progress = getMultiStackProgress();
581
- let latestEvent = "";
582
613
  let latestResourceId = "";
583
614
  verbose(`[${stackName}] Starting to wait for stack operation...`);
584
615
  try {
@@ -610,7 +641,6 @@ async function waitForStackWithProgress(client, stackName, accountId, region, op
610
641
  const logicalId = event.LogicalResourceId;
611
642
  const status = event.ResourceStatus || "";
612
643
  if (logicalId && logicalId !== stackName) {
613
- latestEvent = status;
614
644
  latestResourceId = logicalId;
615
645
  verbose(`[${stackName}] Resource ${logicalId}: ${status}`);
616
646
  if (isResourceComplete(status)) {
@@ -624,10 +654,11 @@ async function waitForStackWithProgress(client, stackName, accountId, region, op
624
654
  } else if (currentStatus.includes("FAILED")) {
625
655
  displayStatus = "failed";
626
656
  }
627
- progress.updateStack(stackName, accountId, region, completedResources.size, displayStatus, latestEvent, latestResourceId);
657
+ progress.updateStack(stackName, accountId, region, completedResources.size, displayStatus, currentStatus, latestResourceId);
628
658
  if (TERMINAL_STATES.has(currentStatus)) {
629
659
  const success2 = SUCCESS_STATES.has(currentStatus);
630
- progress.completeStack(stackName, accountId, region, success2);
660
+ const failureReason = success2 ? void 0 : currentStatus;
661
+ progress.completeStack(stackName, accountId, region, success2, failureReason);
631
662
  verbose(`[${stackName}] Reached terminal state: ${currentStatus} (success: ${success2})`);
632
663
  if (success2) {
633
664
  return;
@@ -2751,6 +2782,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
2751
2782
  const resourceCount = stack.dockerArtifacts.length + stack.bundleArtifacts.length + 2;
2752
2783
  progress.addStack(stack.stackName, stack.accountId, stack.region, resourceCount);
2753
2784
  }
2785
+ progress.start();
2754
2786
  const orgPromise = (async () => {
2755
2787
  try {
2756
2788
  await deployOrgStack(plan, pipelines, authData, currentAccountId, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devramps/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "DevRamps CLI - Bootstrap AWS infrastructure for CI/CD pipelines",
5
5
  "main": "dist/index.js",
6
6
  "bin": {