@devramps/cli 0.1.4 → 0.1.5
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 +88 -48
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -75,6 +75,9 @@ var CloudFormationError = class extends DevRampsError {
|
|
|
75
75
|
import chalk from "chalk";
|
|
76
76
|
var verboseMode = false;
|
|
77
77
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
78
|
+
function getStackKey(stackName, accountId, region) {
|
|
79
|
+
return `${stackName}:${accountId}:${region}`;
|
|
80
|
+
}
|
|
78
81
|
var MultiStackProgress = class {
|
|
79
82
|
stacks = /* @__PURE__ */ new Map();
|
|
80
83
|
stackOrder = [];
|
|
@@ -96,7 +99,11 @@ var MultiStackProgress = class {
|
|
|
96
99
|
* Register a stack to track
|
|
97
100
|
*/
|
|
98
101
|
addStack(stackName, accountId, region, totalResources) {
|
|
99
|
-
|
|
102
|
+
const key = getStackKey(stackName, accountId, region);
|
|
103
|
+
if (this.stacks.has(key)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.stacks.set(key, {
|
|
100
107
|
stackName,
|
|
101
108
|
accountId,
|
|
102
109
|
region,
|
|
@@ -104,14 +111,15 @@ var MultiStackProgress = class {
|
|
|
104
111
|
total: totalResources,
|
|
105
112
|
status: "pending"
|
|
106
113
|
});
|
|
107
|
-
this.stackOrder.push(
|
|
114
|
+
this.stackOrder.push(key);
|
|
108
115
|
this.render();
|
|
109
116
|
}
|
|
110
117
|
/**
|
|
111
118
|
* Update a stack's progress
|
|
112
119
|
*/
|
|
113
|
-
updateStack(stackName, completed, status, latestEvent, latestResourceId) {
|
|
114
|
-
const
|
|
120
|
+
updateStack(stackName, accountId, region, completed, status, latestEvent, latestResourceId) {
|
|
121
|
+
const key = getStackKey(stackName, accountId, region);
|
|
122
|
+
const stack = this.stacks.get(key);
|
|
115
123
|
if (stack) {
|
|
116
124
|
stack.completed = completed;
|
|
117
125
|
stack.status = status;
|
|
@@ -123,8 +131,9 @@ var MultiStackProgress = class {
|
|
|
123
131
|
/**
|
|
124
132
|
* Mark a stack as started
|
|
125
133
|
*/
|
|
126
|
-
startStack(stackName) {
|
|
127
|
-
const
|
|
134
|
+
startStack(stackName, accountId, region) {
|
|
135
|
+
const key = getStackKey(stackName, accountId, region);
|
|
136
|
+
const stack = this.stacks.get(key);
|
|
128
137
|
if (stack) {
|
|
129
138
|
stack.status = "in_progress";
|
|
130
139
|
this.render();
|
|
@@ -133,8 +142,9 @@ var MultiStackProgress = class {
|
|
|
133
142
|
/**
|
|
134
143
|
* Mark a stack as complete
|
|
135
144
|
*/
|
|
136
|
-
completeStack(stackName, success2) {
|
|
137
|
-
const
|
|
145
|
+
completeStack(stackName, accountId, region, success2) {
|
|
146
|
+
const key = getStackKey(stackName, accountId, region);
|
|
147
|
+
const stack = this.stacks.get(key);
|
|
138
148
|
if (stack) {
|
|
139
149
|
stack.status = success2 ? "complete" : "failed";
|
|
140
150
|
stack.completed = success2 ? stack.total : stack.completed;
|
|
@@ -166,8 +176,8 @@ var MultiStackProgress = class {
|
|
|
166
176
|
* Render final state (no clearing, just print)
|
|
167
177
|
*/
|
|
168
178
|
renderFinal() {
|
|
169
|
-
for (const
|
|
170
|
-
const stack = this.stacks.get(
|
|
179
|
+
for (const key of this.stackOrder) {
|
|
180
|
+
const stack = this.stacks.get(key);
|
|
171
181
|
if (!stack) continue;
|
|
172
182
|
console.log(this.formatStackLine(stack, false));
|
|
173
183
|
}
|
|
@@ -201,37 +211,45 @@ var MultiStackProgress = class {
|
|
|
201
211
|
const filled = Math.round(this.barWidth * percentage);
|
|
202
212
|
const empty = this.barWidth - filled;
|
|
203
213
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
204
|
-
const
|
|
214
|
+
const shortAccount = accountId.slice(-4);
|
|
215
|
+
const accountLabel = `[...${shortAccount}/${region}]`;
|
|
205
216
|
const countLabel = `(${completed}/${total})`;
|
|
206
217
|
let eventLabel = "";
|
|
207
|
-
if (latestEvent && latestResourceId) {
|
|
208
|
-
const
|
|
209
|
-
const resourceIdTrunc = latestResourceId.length >
|
|
210
|
-
eventLabel = ` ${
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
const line = `${statusIndicator} ${accountLabel} ${stackName} [${bar}] ${countLabel}${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}`;
|
|
216
226
|
return colorFn(line);
|
|
217
227
|
}
|
|
218
228
|
/**
|
|
219
229
|
* Render all stack progress bars
|
|
220
230
|
*/
|
|
221
231
|
render() {
|
|
222
|
-
if (
|
|
223
|
-
this.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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;
|
|
232
250
|
}
|
|
233
|
-
this.lastLineCount = lines.length;
|
|
234
251
|
}
|
|
252
|
+
isRendering = false;
|
|
235
253
|
};
|
|
236
254
|
var globalProgress = null;
|
|
237
255
|
function getMultiStackProgress() {
|
|
@@ -409,10 +427,12 @@ import {
|
|
|
409
427
|
DescribeStackEventsCommand,
|
|
410
428
|
CreateStackCommand,
|
|
411
429
|
UpdateStackCommand,
|
|
430
|
+
DeleteStackCommand,
|
|
412
431
|
CreateChangeSetCommand,
|
|
413
432
|
DescribeChangeSetCommand,
|
|
414
433
|
DeleteChangeSetCommand,
|
|
415
434
|
waitUntilChangeSetCreateComplete,
|
|
435
|
+
waitUntilStackDeleteComplete,
|
|
416
436
|
ChangeSetType
|
|
417
437
|
} from "@aws-sdk/client-cloudformation";
|
|
418
438
|
async function getStackStatus(stackName, credentials, region) {
|
|
@@ -552,14 +572,15 @@ function isResourceComplete(status) {
|
|
|
552
572
|
if (!status) return false;
|
|
553
573
|
return status.includes("_COMPLETE") && !status.includes("ROLLBACK");
|
|
554
574
|
}
|
|
555
|
-
async function waitForStackWithProgress(client, stackName, operationStartTime, totalResources, maxWaitTime = 600) {
|
|
575
|
+
async function waitForStackWithProgress(client, stackName, accountId, region, operationStartTime, totalResources, maxWaitTime = 600) {
|
|
556
576
|
const seenEventIds = /* @__PURE__ */ new Set();
|
|
557
577
|
const completedResources = /* @__PURE__ */ new Set();
|
|
558
578
|
const startTime = Date.now();
|
|
559
|
-
const pollInterval =
|
|
579
|
+
const pollInterval = 2e3;
|
|
560
580
|
const progress = getMultiStackProgress();
|
|
561
581
|
let latestEvent = "";
|
|
562
582
|
let latestResourceId = "";
|
|
583
|
+
verbose(`[${stackName}] Starting to wait for stack operation...`);
|
|
563
584
|
try {
|
|
564
585
|
while (true) {
|
|
565
586
|
if (Date.now() - startTime > maxWaitTime * 1e3) {
|
|
@@ -572,6 +593,8 @@ async function waitForStackWithProgress(client, stackName, operationStartTime, t
|
|
|
572
593
|
if (!stack) {
|
|
573
594
|
throw new Error(`Stack ${stackName} not found`);
|
|
574
595
|
}
|
|
596
|
+
const currentStatus = stack.StackStatus || "";
|
|
597
|
+
verbose(`[${stackName}] Current status: ${currentStatus}`);
|
|
575
598
|
const eventsResponse = await client.send(
|
|
576
599
|
new DescribeStackEventsCommand({ StackName: stackName })
|
|
577
600
|
);
|
|
@@ -589,22 +612,23 @@ async function waitForStackWithProgress(client, stackName, operationStartTime, t
|
|
|
589
612
|
if (logicalId && logicalId !== stackName) {
|
|
590
613
|
latestEvent = status;
|
|
591
614
|
latestResourceId = logicalId;
|
|
615
|
+
verbose(`[${stackName}] Resource ${logicalId}: ${status}`);
|
|
592
616
|
if (isResourceComplete(status)) {
|
|
593
617
|
completedResources.add(logicalId);
|
|
594
618
|
}
|
|
595
619
|
}
|
|
596
620
|
}
|
|
597
|
-
const currentStatus = stack.StackStatus || "";
|
|
598
621
|
let displayStatus = "in_progress";
|
|
599
622
|
if (currentStatus.includes("ROLLBACK")) {
|
|
600
623
|
displayStatus = "rollback";
|
|
601
624
|
} else if (currentStatus.includes("FAILED")) {
|
|
602
625
|
displayStatus = "failed";
|
|
603
626
|
}
|
|
604
|
-
progress.updateStack(stackName, completedResources.size, displayStatus, latestEvent, latestResourceId);
|
|
627
|
+
progress.updateStack(stackName, accountId, region, completedResources.size, displayStatus, latestEvent, latestResourceId);
|
|
605
628
|
if (TERMINAL_STATES.has(currentStatus)) {
|
|
606
629
|
const success2 = SUCCESS_STATES.has(currentStatus);
|
|
607
|
-
progress.completeStack(stackName, success2);
|
|
630
|
+
progress.completeStack(stackName, accountId, region, success2);
|
|
631
|
+
verbose(`[${stackName}] Reached terminal state: ${currentStatus} (success: ${success2})`);
|
|
608
632
|
if (success2) {
|
|
609
633
|
return;
|
|
610
634
|
}
|
|
@@ -613,12 +637,12 @@ async function waitForStackWithProgress(client, stackName, operationStartTime, t
|
|
|
613
637
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
614
638
|
}
|
|
615
639
|
} catch (error2) {
|
|
616
|
-
progress.completeStack(stackName, false);
|
|
640
|
+
progress.completeStack(stackName, accountId, region, false);
|
|
617
641
|
throw error2;
|
|
618
642
|
}
|
|
619
643
|
}
|
|
620
644
|
async function deployStack(options) {
|
|
621
|
-
const { stackName, template, accountId, region, credentials } = options;
|
|
645
|
+
const { stackName, template, accountId, region = "us-east-1", credentials } = options;
|
|
622
646
|
const client = new CloudFormationClient({
|
|
623
647
|
credentials,
|
|
624
648
|
region
|
|
@@ -626,28 +650,44 @@ async function deployStack(options) {
|
|
|
626
650
|
const templateBody = JSON.stringify(template);
|
|
627
651
|
const resourceCount = Object.keys(template.Resources || {}).length;
|
|
628
652
|
const progress = getMultiStackProgress();
|
|
629
|
-
progress.startStack(stackName);
|
|
653
|
+
progress.startStack(stackName, accountId, region);
|
|
630
654
|
try {
|
|
631
655
|
const stackStatus = await getStackStatus(stackName, credentials, region);
|
|
632
|
-
if (stackStatus.exists) {
|
|
656
|
+
if (stackStatus.exists && stackStatus.status === "ROLLBACK_COMPLETE") {
|
|
657
|
+
verbose(`Stack ${stackName} is in ROLLBACK_COMPLETE state, deleting before recreating...`);
|
|
658
|
+
await deleteStack(client, stackName);
|
|
659
|
+
verbose(`Stack ${stackName} deleted, now creating...`);
|
|
660
|
+
await createStack(client, stackName, accountId, region, templateBody, resourceCount);
|
|
661
|
+
} else if (stackStatus.exists) {
|
|
633
662
|
verbose(`Stack ${stackName} exists, updating...`);
|
|
634
|
-
await updateStack(client, stackName,
|
|
663
|
+
await updateStack(client, stackName, accountId, region, templateBody, resourceCount);
|
|
635
664
|
} else {
|
|
636
665
|
verbose(`Stack ${stackName} does not exist, creating...`);
|
|
637
|
-
await createStack(client, stackName,
|
|
666
|
+
await createStack(client, stackName, accountId, region, templateBody, resourceCount);
|
|
638
667
|
}
|
|
639
668
|
} catch (error2) {
|
|
640
669
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
641
670
|
if (errorMessage.includes("No updates are to be performed")) {
|
|
642
671
|
verbose(`Stack ${stackName} is already up to date`);
|
|
643
|
-
progress.completeStack(stackName, true);
|
|
672
|
+
progress.completeStack(stackName, accountId, region, true);
|
|
644
673
|
return;
|
|
645
674
|
}
|
|
646
|
-
progress.completeStack(stackName, false);
|
|
675
|
+
progress.completeStack(stackName, accountId, region, false);
|
|
647
676
|
throw new CloudFormationError(stackName, accountId, errorMessage);
|
|
648
677
|
}
|
|
649
678
|
}
|
|
650
|
-
async function
|
|
679
|
+
async function deleteStack(client, stackName) {
|
|
680
|
+
await client.send(
|
|
681
|
+
new DeleteStackCommand({
|
|
682
|
+
StackName: stackName
|
|
683
|
+
})
|
|
684
|
+
);
|
|
685
|
+
await waitUntilStackDeleteComplete(
|
|
686
|
+
{ client, maxWaitTime: 300 },
|
|
687
|
+
{ StackName: stackName }
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
async function createStack(client, stackName, accountId, region, templateBody, resourceCount) {
|
|
651
691
|
const operationStartTime = /* @__PURE__ */ new Date();
|
|
652
692
|
await client.send(
|
|
653
693
|
new CreateStackCommand({
|
|
@@ -660,9 +700,9 @@ async function createStack(client, stackName, templateBody, _accountId, resource
|
|
|
660
700
|
]
|
|
661
701
|
})
|
|
662
702
|
);
|
|
663
|
-
await waitForStackWithProgress(client, stackName, operationStartTime, resourceCount);
|
|
703
|
+
await waitForStackWithProgress(client, stackName, accountId, region, operationStartTime, resourceCount);
|
|
664
704
|
}
|
|
665
|
-
async function updateStack(client, stackName,
|
|
705
|
+
async function updateStack(client, stackName, accountId, region, templateBody, resourceCount) {
|
|
666
706
|
const operationStartTime = /* @__PURE__ */ new Date();
|
|
667
707
|
await client.send(
|
|
668
708
|
new UpdateStackCommand({
|
|
@@ -671,7 +711,7 @@ async function updateStack(client, stackName, templateBody, _accountId, resource
|
|
|
671
711
|
Capabilities: ["CAPABILITY_NAMED_IAM"]
|
|
672
712
|
})
|
|
673
713
|
);
|
|
674
|
-
await waitForStackWithProgress(client, stackName, operationStartTime, resourceCount);
|
|
714
|
+
await waitForStackWithProgress(client, stackName, accountId, region, operationStartTime, resourceCount);
|
|
675
715
|
}
|
|
676
716
|
async function readExistingStack(stackName, accountId, region, credentials) {
|
|
677
717
|
const client = new CloudFormationClient({
|