@devramps/cli 0.1.6 → 0.1.8
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 +128 -62
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -81,30 +81,54 @@ 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
|
|
93
|
+
maxStackNameLen = 40;
|
|
94
|
+
// Will be calculated dynamically
|
|
89
95
|
constructor() {
|
|
90
96
|
this.isTTY = process.stdout.isTTY ?? false;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Start the progress display (call after all stacks are registered)
|
|
100
|
+
*/
|
|
101
|
+
start() {
|
|
102
|
+
this.maxStackNameLen = Math.max(
|
|
103
|
+
...Array.from(this.stacks.values()).map((s) => s.stackName.length),
|
|
104
|
+
20
|
|
105
|
+
// minimum width
|
|
106
|
+
);
|
|
91
107
|
if (this.isTTY) {
|
|
108
|
+
if (this.useAltScreen) {
|
|
109
|
+
process.stdout.write("\x1B[?1049h");
|
|
110
|
+
}
|
|
111
|
+
process.stdout.write("\x1B[?25l");
|
|
112
|
+
process.stdout.write("\x1B[H");
|
|
113
|
+
process.stdout.write("\x1B[2J");
|
|
114
|
+
this.doRender();
|
|
92
115
|
this.spinnerInterval = setInterval(() => {
|
|
93
116
|
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
94
|
-
this.
|
|
95
|
-
},
|
|
117
|
+
this.scheduleRender();
|
|
118
|
+
}, 100);
|
|
96
119
|
}
|
|
97
120
|
}
|
|
98
121
|
/**
|
|
99
122
|
* Register a stack to track
|
|
100
123
|
*/
|
|
101
|
-
addStack(stackName, accountId, region, totalResources) {
|
|
124
|
+
addStack(stackName, stackType, accountId, region, totalResources) {
|
|
102
125
|
const key = getStackKey(stackName, accountId, region);
|
|
103
126
|
if (this.stacks.has(key)) {
|
|
104
127
|
return;
|
|
105
128
|
}
|
|
106
129
|
this.stacks.set(key, {
|
|
107
130
|
stackName,
|
|
131
|
+
stackType,
|
|
108
132
|
accountId,
|
|
109
133
|
region,
|
|
110
134
|
completed: 0,
|
|
@@ -112,20 +136,19 @@ var MultiStackProgress = class {
|
|
|
112
136
|
status: "pending"
|
|
113
137
|
});
|
|
114
138
|
this.stackOrder.push(key);
|
|
115
|
-
this.render();
|
|
116
139
|
}
|
|
117
140
|
/**
|
|
118
141
|
* Update a stack's progress
|
|
119
142
|
*/
|
|
120
|
-
updateStack(stackName, accountId, region, completed, status,
|
|
143
|
+
updateStack(stackName, accountId, region, completed, status, cfnStatus, latestResourceId) {
|
|
121
144
|
const key = getStackKey(stackName, accountId, region);
|
|
122
145
|
const stack = this.stacks.get(key);
|
|
123
146
|
if (stack) {
|
|
124
147
|
stack.completed = completed;
|
|
125
148
|
stack.status = status;
|
|
126
|
-
if (
|
|
149
|
+
if (cfnStatus) stack.cfnStatus = cfnStatus;
|
|
127
150
|
if (latestResourceId) stack.latestResourceId = latestResourceId;
|
|
128
|
-
this.
|
|
151
|
+
this.scheduleRender();
|
|
129
152
|
}
|
|
130
153
|
}
|
|
131
154
|
/**
|
|
@@ -136,30 +159,40 @@ var MultiStackProgress = class {
|
|
|
136
159
|
const stack = this.stacks.get(key);
|
|
137
160
|
if (stack) {
|
|
138
161
|
stack.status = "in_progress";
|
|
139
|
-
|
|
162
|
+
stack.cfnStatus = "STARTING";
|
|
163
|
+
this.scheduleRender();
|
|
140
164
|
}
|
|
141
165
|
}
|
|
142
166
|
/**
|
|
143
167
|
* Mark a stack as complete
|
|
144
168
|
*/
|
|
145
|
-
completeStack(stackName, accountId, region, success2) {
|
|
169
|
+
completeStack(stackName, accountId, region, success2, failureReason) {
|
|
146
170
|
const key = getStackKey(stackName, accountId, region);
|
|
147
171
|
const stack = this.stacks.get(key);
|
|
148
172
|
if (stack) {
|
|
149
173
|
stack.status = success2 ? "complete" : "failed";
|
|
150
174
|
stack.completed = success2 ? stack.total : stack.completed;
|
|
151
|
-
|
|
175
|
+
if (failureReason) stack.failureReason = failureReason;
|
|
176
|
+
this.scheduleRender();
|
|
152
177
|
}
|
|
153
178
|
}
|
|
154
179
|
/**
|
|
155
|
-
*
|
|
180
|
+
* Schedule a render (debounced to prevent too many updates)
|
|
156
181
|
*/
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
scheduleRender() {
|
|
183
|
+
if (this.renderScheduled) return;
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
const timeSinceLastRender = now - this.lastRenderTime;
|
|
186
|
+
const minInterval = 50;
|
|
187
|
+
if (timeSinceLastRender >= minInterval) {
|
|
188
|
+
this.doRender();
|
|
189
|
+
} else {
|
|
190
|
+
this.renderScheduled = true;
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
this.renderScheduled = false;
|
|
193
|
+
this.doRender();
|
|
194
|
+
}, minInterval - timeSinceLastRender);
|
|
161
195
|
}
|
|
162
|
-
this.lastLineCount = 0;
|
|
163
196
|
}
|
|
164
197
|
/**
|
|
165
198
|
* Finish and stop updates
|
|
@@ -169,7 +202,12 @@ var MultiStackProgress = class {
|
|
|
169
202
|
clearInterval(this.spinnerInterval);
|
|
170
203
|
this.spinnerInterval = null;
|
|
171
204
|
}
|
|
172
|
-
this.
|
|
205
|
+
if (this.isTTY) {
|
|
206
|
+
process.stdout.write("\x1B[?25h");
|
|
207
|
+
if (this.useAltScreen) {
|
|
208
|
+
process.stdout.write("\x1B[?1049l");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
173
211
|
this.renderFinal();
|
|
174
212
|
}
|
|
175
213
|
/**
|
|
@@ -182,11 +220,26 @@ var MultiStackProgress = class {
|
|
|
182
220
|
console.log(this.formatStackLine(stack, false));
|
|
183
221
|
}
|
|
184
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Get a short label for stack type
|
|
225
|
+
*/
|
|
226
|
+
getTypeLabel(stackType) {
|
|
227
|
+
switch (stackType) {
|
|
228
|
+
case "org":
|
|
229
|
+
return "ORG";
|
|
230
|
+
case "pipeline":
|
|
231
|
+
return "PIPE";
|
|
232
|
+
case "account":
|
|
233
|
+
return "ACCT";
|
|
234
|
+
case "stage":
|
|
235
|
+
return "STAGE";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
185
238
|
/**
|
|
186
239
|
* Format a single stack line
|
|
187
240
|
*/
|
|
188
241
|
formatStackLine(stack, withSpinner) {
|
|
189
|
-
const { accountId, region, stackName, completed, total, status,
|
|
242
|
+
const { accountId, region, stackName, stackType, completed, total, status, cfnStatus, latestResourceId, failureReason } = stack;
|
|
190
243
|
let statusIndicator;
|
|
191
244
|
let colorFn;
|
|
192
245
|
switch (status) {
|
|
@@ -201,7 +254,7 @@ var MultiStackProgress = class {
|
|
|
201
254
|
break;
|
|
202
255
|
case "in_progress":
|
|
203
256
|
statusIndicator = withSpinner ? SPINNER_FRAMES[this.spinnerFrame] : "\u22EF";
|
|
204
|
-
colorFn = chalk.
|
|
257
|
+
colorFn = chalk.cyanBright;
|
|
205
258
|
break;
|
|
206
259
|
default:
|
|
207
260
|
statusIndicator = "\u25CB";
|
|
@@ -211,45 +264,53 @@ var MultiStackProgress = class {
|
|
|
211
264
|
const filled = Math.round(this.barWidth * percentage);
|
|
212
265
|
const empty = this.barWidth - filled;
|
|
213
266
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
214
|
-
const
|
|
215
|
-
const accountLabel =
|
|
216
|
-
const countLabel =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
267
|
+
const typeLabel = this.getTypeLabel(stackType).padEnd(5);
|
|
268
|
+
const accountLabel = `${accountId} ${region.padEnd(12)}`;
|
|
269
|
+
const countLabel = `${completed}/${total}`;
|
|
270
|
+
const displayName = stackName.padEnd(this.maxStackNameLen);
|
|
271
|
+
let rightInfo = "";
|
|
272
|
+
if (status === "failed" || status === "rollback") {
|
|
273
|
+
const statusText = cfnStatus || "FAILED";
|
|
274
|
+
if (failureReason && failureReason !== cfnStatus) {
|
|
275
|
+
const maxLen = 50;
|
|
276
|
+
const fullReason = `${statusText}: ${failureReason}`;
|
|
277
|
+
rightInfo = fullReason.length > maxLen ? fullReason.slice(0, maxLen - 3) + "..." : fullReason;
|
|
278
|
+
} else {
|
|
279
|
+
rightInfo = statusText;
|
|
280
|
+
}
|
|
281
|
+
} else if (status === "in_progress") {
|
|
282
|
+
const cfnStatusDisplay = cfnStatus || "DEPLOYING";
|
|
283
|
+
const resourceDisplay = latestResourceId ? latestResourceId.length > 25 ? latestResourceId.slice(0, 22) + "..." : latestResourceId : "";
|
|
284
|
+
rightInfo = resourceDisplay ? `${cfnStatusDisplay} \u2192 ${resourceDisplay}` : cfnStatusDisplay;
|
|
285
|
+
} else if (status === "complete") {
|
|
286
|
+
rightInfo = cfnStatus || "COMPLETE";
|
|
287
|
+
}
|
|
288
|
+
const leftPart = `${statusIndicator} [${typeLabel}] ${accountLabel} ${displayName}`;
|
|
289
|
+
const middlePart = `[${bar}] ${countLabel}`;
|
|
290
|
+
const line = `${leftPart} ${middlePart} ${rightInfo}`;
|
|
226
291
|
return colorFn(line);
|
|
227
292
|
}
|
|
228
293
|
/**
|
|
229
|
-
*
|
|
294
|
+
* Perform the actual render
|
|
230
295
|
*/
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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;
|
|
296
|
+
doRender() {
|
|
297
|
+
this.lastRenderTime = Date.now();
|
|
298
|
+
if (!this.isTTY) return;
|
|
299
|
+
process.stdout.write("\x1B[H");
|
|
300
|
+
process.stdout.write(chalk.bold.underline("Deploying Stacks") + "\x1B[K\n\n");
|
|
301
|
+
for (const key of this.stackOrder) {
|
|
302
|
+
const stack = this.stacks.get(key);
|
|
303
|
+
if (!stack) continue;
|
|
304
|
+
process.stdout.write(this.formatStackLine(stack, true) + "\x1B[K\n");
|
|
250
305
|
}
|
|
306
|
+
const completed = Array.from(this.stacks.values()).filter((s) => s.status === "complete").length;
|
|
307
|
+
const failed = Array.from(this.stacks.values()).filter((s) => s.status === "failed" || s.status === "rollback").length;
|
|
308
|
+
const inProgress = Array.from(this.stacks.values()).filter((s) => s.status === "in_progress").length;
|
|
309
|
+
const pending = Array.from(this.stacks.values()).filter((s) => s.status === "pending").length;
|
|
310
|
+
process.stdout.write("\n");
|
|
311
|
+
process.stdout.write(chalk.gray(`Progress: ${completed} complete, ${inProgress} in progress, ${pending} pending, ${failed} failed`) + "\x1B[K\n");
|
|
312
|
+
this.hasRenderedOnce = true;
|
|
251
313
|
}
|
|
252
|
-
isRendering = false;
|
|
253
314
|
};
|
|
254
315
|
var globalProgress = null;
|
|
255
316
|
function getMultiStackProgress() {
|
|
@@ -572,14 +633,14 @@ function isResourceComplete(status) {
|
|
|
572
633
|
if (!status) return false;
|
|
573
634
|
return status.includes("_COMPLETE") && !status.includes("ROLLBACK");
|
|
574
635
|
}
|
|
575
|
-
async function waitForStackWithProgress(client, stackName, accountId, region, operationStartTime,
|
|
636
|
+
async function waitForStackWithProgress(client, stackName, accountId, region, operationStartTime, _totalResources, maxWaitTime = 600) {
|
|
576
637
|
const seenEventIds = /* @__PURE__ */ new Set();
|
|
577
638
|
const completedResources = /* @__PURE__ */ new Set();
|
|
578
639
|
const startTime = Date.now();
|
|
579
640
|
const pollInterval = 2e3;
|
|
580
641
|
const progress = getMultiStackProgress();
|
|
581
|
-
let latestEvent = "";
|
|
582
642
|
let latestResourceId = "";
|
|
643
|
+
let latestFailureReason = "";
|
|
583
644
|
verbose(`[${stackName}] Starting to wait for stack operation...`);
|
|
584
645
|
try {
|
|
585
646
|
while (true) {
|
|
@@ -610,12 +671,15 @@ async function waitForStackWithProgress(client, stackName, accountId, region, op
|
|
|
610
671
|
const logicalId = event.LogicalResourceId;
|
|
611
672
|
const status = event.ResourceStatus || "";
|
|
612
673
|
if (logicalId && logicalId !== stackName) {
|
|
613
|
-
latestEvent = status;
|
|
614
674
|
latestResourceId = logicalId;
|
|
615
675
|
verbose(`[${stackName}] Resource ${logicalId}: ${status}`);
|
|
616
676
|
if (isResourceComplete(status)) {
|
|
617
677
|
completedResources.add(logicalId);
|
|
618
678
|
}
|
|
679
|
+
if (status.includes("FAILED") && event.ResourceStatusReason) {
|
|
680
|
+
latestFailureReason = `${logicalId}: ${event.ResourceStatusReason}`;
|
|
681
|
+
verbose(`[${stackName}] Failure reason: ${latestFailureReason}`);
|
|
682
|
+
}
|
|
619
683
|
}
|
|
620
684
|
}
|
|
621
685
|
let displayStatus = "in_progress";
|
|
@@ -624,10 +688,11 @@ async function waitForStackWithProgress(client, stackName, accountId, region, op
|
|
|
624
688
|
} else if (currentStatus.includes("FAILED")) {
|
|
625
689
|
displayStatus = "failed";
|
|
626
690
|
}
|
|
627
|
-
progress.updateStack(stackName, accountId, region, completedResources.size, displayStatus,
|
|
691
|
+
progress.updateStack(stackName, accountId, region, completedResources.size, displayStatus, currentStatus, latestResourceId);
|
|
628
692
|
if (TERMINAL_STATES.has(currentStatus)) {
|
|
629
693
|
const success2 = SUCCESS_STATES.has(currentStatus);
|
|
630
|
-
|
|
694
|
+
const failureReason = success2 ? void 0 : latestFailureReason || currentStatus;
|
|
695
|
+
progress.completeStack(stackName, accountId, region, success2, failureReason);
|
|
631
696
|
verbose(`[${stackName}] Reached terminal state: ${currentStatus} (success: ${success2})`);
|
|
632
697
|
if (success2) {
|
|
633
698
|
return;
|
|
@@ -2739,18 +2804,19 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
|
|
|
2739
2804
|
info(`Deploying ${totalStacks} stack(s) in parallel...`);
|
|
2740
2805
|
newline();
|
|
2741
2806
|
const progress = getMultiStackProgress();
|
|
2742
|
-
progress.addStack(plan.orgStack.stackName, plan.orgStack.accountId, plan.orgStack.region, 5);
|
|
2807
|
+
progress.addStack(plan.orgStack.stackName, "org", plan.orgStack.accountId, plan.orgStack.region, 5);
|
|
2743
2808
|
for (const stack of plan.pipelineStacks) {
|
|
2744
2809
|
const resourceCount = stack.dockerArtifacts.length + stack.bundleArtifacts.length;
|
|
2745
|
-
progress.addStack(stack.stackName, stack.accountId, stack.region, Math.max(resourceCount, 1));
|
|
2810
|
+
progress.addStack(stack.stackName, "pipeline", stack.accountId, stack.region, Math.max(resourceCount, 1));
|
|
2746
2811
|
}
|
|
2747
2812
|
for (const stack of plan.accountStacks) {
|
|
2748
|
-
progress.addStack(stack.stackName, stack.accountId, stack.region, 1);
|
|
2813
|
+
progress.addStack(stack.stackName, "account", stack.accountId, stack.region, 1);
|
|
2749
2814
|
}
|
|
2750
2815
|
for (const stack of plan.stageStacks) {
|
|
2751
2816
|
const resourceCount = stack.dockerArtifacts.length + stack.bundleArtifacts.length + 2;
|
|
2752
|
-
progress.addStack(stack.stackName, stack.accountId, stack.region, resourceCount);
|
|
2817
|
+
progress.addStack(stack.stackName, "stage", stack.accountId, stack.region, resourceCount);
|
|
2753
2818
|
}
|
|
2819
|
+
progress.start();
|
|
2754
2820
|
const orgPromise = (async () => {
|
|
2755
2821
|
try {
|
|
2756
2822
|
await deployOrgStack(plan, pipelines, authData, currentAccountId, options);
|