@go-to-k/cdkd 0.3.6 → 0.4.1
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/README.md +7 -1
- package/dist/cli.js +291 -88
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.4.1.tgz +0 -0
- package/dist/index.js +217 -57
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.3.6.tgz +0 -0
package/README.md
CHANGED
|
@@ -380,9 +380,15 @@ cdkd deploy Stack1 Stack2
|
|
|
380
380
|
# Deploy all stacks
|
|
381
381
|
cdkd deploy --all
|
|
382
382
|
|
|
383
|
-
# Deploy with wildcard
|
|
383
|
+
# Deploy with wildcard (matched against the physical CloudFormation stack name)
|
|
384
384
|
cdkd deploy 'My*'
|
|
385
385
|
|
|
386
|
+
# Deploy stacks under a CDK Stage using the hierarchical path (CDK CLI parity)
|
|
387
|
+
# Patterns containing '/' are routed to the CDK display path; both forms work:
|
|
388
|
+
cdkd deploy 'MyStage/*' # all stacks under MyStage
|
|
389
|
+
cdkd deploy MyStage/Api # specific stack by display path
|
|
390
|
+
cdkd deploy MyStage-Api # same stack by physical CloudFormation name
|
|
391
|
+
|
|
386
392
|
# Deploy with context values
|
|
387
393
|
cdkd deploy -c env=staging -c featureFlag=true
|
|
388
394
|
|
package/dist/cli.js
CHANGED
|
@@ -527,6 +527,141 @@ var destroyOptions = [
|
|
|
527
527
|
)
|
|
528
528
|
];
|
|
529
529
|
|
|
530
|
+
// src/utils/live-renderer.ts
|
|
531
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
532
|
+
var FRAME_INTERVAL_MS = 80;
|
|
533
|
+
var ESC = "\x1B[";
|
|
534
|
+
var LiveRenderer = class {
|
|
535
|
+
constructor(stream = process.stdout) {
|
|
536
|
+
this.stream = stream;
|
|
537
|
+
}
|
|
538
|
+
tasks = /* @__PURE__ */ new Map();
|
|
539
|
+
active = false;
|
|
540
|
+
spinnerIndex = 0;
|
|
541
|
+
interval = null;
|
|
542
|
+
linesDrawn = 0;
|
|
543
|
+
cursorHidden = false;
|
|
544
|
+
exitListener = null;
|
|
545
|
+
isActive() {
|
|
546
|
+
return this.active;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Enable the live renderer. No-op if stdout is not a TTY or if
|
|
550
|
+
* `CDKD_NO_LIVE=1`. Returns true if successfully enabled.
|
|
551
|
+
*/
|
|
552
|
+
start() {
|
|
553
|
+
if (this.active)
|
|
554
|
+
return true;
|
|
555
|
+
if (!this.stream.isTTY)
|
|
556
|
+
return false;
|
|
557
|
+
if (process.env["CDKD_NO_LIVE"] === "1")
|
|
558
|
+
return false;
|
|
559
|
+
this.active = true;
|
|
560
|
+
this.hideCursor();
|
|
561
|
+
if (!this.exitListener) {
|
|
562
|
+
this.exitListener = () => this.showCursor();
|
|
563
|
+
process.on("exit", this.exitListener);
|
|
564
|
+
}
|
|
565
|
+
this.interval = setInterval(() => this.draw(), FRAME_INTERVAL_MS);
|
|
566
|
+
if (typeof this.interval.unref === "function")
|
|
567
|
+
this.interval.unref();
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
stop() {
|
|
571
|
+
if (!this.active)
|
|
572
|
+
return;
|
|
573
|
+
if (this.interval) {
|
|
574
|
+
clearInterval(this.interval);
|
|
575
|
+
this.interval = null;
|
|
576
|
+
}
|
|
577
|
+
this.clear();
|
|
578
|
+
this.showCursor();
|
|
579
|
+
if (this.exitListener) {
|
|
580
|
+
process.removeListener("exit", this.exitListener);
|
|
581
|
+
this.exitListener = null;
|
|
582
|
+
}
|
|
583
|
+
this.tasks.clear();
|
|
584
|
+
this.active = false;
|
|
585
|
+
}
|
|
586
|
+
addTask(id, label) {
|
|
587
|
+
this.tasks.set(id, { label, startedAt: Date.now() });
|
|
588
|
+
if (this.active)
|
|
589
|
+
this.draw();
|
|
590
|
+
}
|
|
591
|
+
removeTask(id) {
|
|
592
|
+
if (!this.tasks.delete(id))
|
|
593
|
+
return;
|
|
594
|
+
if (this.active)
|
|
595
|
+
this.draw();
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Print content above the live area. Clears the live area, runs the writer,
|
|
599
|
+
* then redraws the live area. When the renderer is inactive, the writer
|
|
600
|
+
* runs directly so callers can use this unconditionally.
|
|
601
|
+
*/
|
|
602
|
+
printAbove(write) {
|
|
603
|
+
if (!this.active) {
|
|
604
|
+
write();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
this.clear();
|
|
608
|
+
write();
|
|
609
|
+
this.draw();
|
|
610
|
+
}
|
|
611
|
+
clear() {
|
|
612
|
+
if (this.linesDrawn === 0)
|
|
613
|
+
return;
|
|
614
|
+
this.stream.write("\r");
|
|
615
|
+
for (let i = 0; i < this.linesDrawn; i++) {
|
|
616
|
+
this.stream.write(`${ESC}1A${ESC}2K`);
|
|
617
|
+
}
|
|
618
|
+
this.linesDrawn = 0;
|
|
619
|
+
}
|
|
620
|
+
draw() {
|
|
621
|
+
if (!this.active)
|
|
622
|
+
return;
|
|
623
|
+
this.clear();
|
|
624
|
+
if (this.tasks.size === 0)
|
|
625
|
+
return;
|
|
626
|
+
const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length];
|
|
627
|
+
this.spinnerIndex++;
|
|
628
|
+
const cols = this.stream.columns ?? 80;
|
|
629
|
+
const lines = [];
|
|
630
|
+
for (const task of this.tasks.values()) {
|
|
631
|
+
const elapsed = ((Date.now() - task.startedAt) / 1e3).toFixed(1);
|
|
632
|
+
const raw = ` ${frame} ${task.label} (${elapsed}s)`;
|
|
633
|
+
lines.push(this.truncate(raw, cols));
|
|
634
|
+
}
|
|
635
|
+
this.stream.write(lines.join("\n") + "\n");
|
|
636
|
+
this.linesDrawn = lines.length;
|
|
637
|
+
}
|
|
638
|
+
truncate(s, maxLen) {
|
|
639
|
+
if (s.length <= maxLen)
|
|
640
|
+
return s;
|
|
641
|
+
if (maxLen <= 1)
|
|
642
|
+
return "\u2026";
|
|
643
|
+
return s.substring(0, maxLen - 1) + "\u2026";
|
|
644
|
+
}
|
|
645
|
+
hideCursor() {
|
|
646
|
+
if (this.cursorHidden)
|
|
647
|
+
return;
|
|
648
|
+
this.stream.write(`${ESC}?25l`);
|
|
649
|
+
this.cursorHidden = true;
|
|
650
|
+
}
|
|
651
|
+
showCursor() {
|
|
652
|
+
if (!this.cursorHidden)
|
|
653
|
+
return;
|
|
654
|
+
this.stream.write(`${ESC}?25h`);
|
|
655
|
+
this.cursorHidden = false;
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
var globalRenderer = null;
|
|
659
|
+
function getLiveRenderer() {
|
|
660
|
+
if (!globalRenderer)
|
|
661
|
+
globalRenderer = new LiveRenderer();
|
|
662
|
+
return globalRenderer;
|
|
663
|
+
}
|
|
664
|
+
|
|
530
665
|
// src/utils/logger.ts
|
|
531
666
|
var colors = {
|
|
532
667
|
reset: "\x1B[0m",
|
|
@@ -583,22 +718,26 @@ var ConsoleLogger = class {
|
|
|
583
718
|
}
|
|
584
719
|
debug(message, ...args) {
|
|
585
720
|
if (this.shouldLog("debug")) {
|
|
586
|
-
|
|
721
|
+
const formatted = this.formatMessage("debug", message, ...args);
|
|
722
|
+
getLiveRenderer().printAbove(() => console.debug(formatted));
|
|
587
723
|
}
|
|
588
724
|
}
|
|
589
725
|
info(message, ...args) {
|
|
590
726
|
if (this.shouldLog("info")) {
|
|
591
|
-
|
|
727
|
+
const formatted = this.formatMessage("info", message, ...args);
|
|
728
|
+
getLiveRenderer().printAbove(() => console.info(formatted));
|
|
592
729
|
}
|
|
593
730
|
}
|
|
594
731
|
warn(message, ...args) {
|
|
595
732
|
if (this.shouldLog("warn")) {
|
|
596
|
-
|
|
733
|
+
const formatted = this.formatMessage("warn", message, ...args);
|
|
734
|
+
getLiveRenderer().printAbove(() => console.warn(formatted));
|
|
597
735
|
}
|
|
598
736
|
}
|
|
599
737
|
error(message, ...args) {
|
|
600
738
|
if (this.shouldLog("error")) {
|
|
601
|
-
|
|
739
|
+
const formatted = this.formatMessage("error", message, ...args);
|
|
740
|
+
getLiveRenderer().printAbove(() => console.error(formatted));
|
|
602
741
|
}
|
|
603
742
|
}
|
|
604
743
|
/**
|
|
@@ -1243,6 +1382,7 @@ var AssemblyReader = class {
|
|
|
1243
1382
|
}
|
|
1244
1383
|
return {
|
|
1245
1384
|
stackName,
|
|
1385
|
+
displayName: artifact.displayName ?? stackName,
|
|
1246
1386
|
artifactId,
|
|
1247
1387
|
template,
|
|
1248
1388
|
assetManifestPath,
|
|
@@ -25839,6 +25979,57 @@ var IMPLICIT_DELETE_DEPENDENCIES = {
|
|
|
25839
25979
|
]
|
|
25840
25980
|
};
|
|
25841
25981
|
|
|
25982
|
+
// src/deployment/retryable-errors.ts
|
|
25983
|
+
var RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
25984
|
+
// IAM propagation
|
|
25985
|
+
"cannot be assumed",
|
|
25986
|
+
"role defined for the function",
|
|
25987
|
+
"not authorized to perform",
|
|
25988
|
+
"execution role",
|
|
25989
|
+
"trust policy",
|
|
25990
|
+
"Role validation failed",
|
|
25991
|
+
"does not have required permissions",
|
|
25992
|
+
"Trusted Entity",
|
|
25993
|
+
"currently in the following state: Pending",
|
|
25994
|
+
// DELETE dependency ordering (parallel deletion race conditions)
|
|
25995
|
+
"has dependencies and cannot be deleted",
|
|
25996
|
+
"can't be deleted since it has",
|
|
25997
|
+
"DependencyViolation",
|
|
25998
|
+
// AWS eventual consistency (dependency just created but not yet visible)
|
|
25999
|
+
// e.g., RDS DBCluster referencing a just-created DBSubnetGroup
|
|
26000
|
+
"does not exist",
|
|
26001
|
+
// AppSync schema is being created asynchronously
|
|
26002
|
+
"Schema is currently being altered",
|
|
26003
|
+
// IAM principal not yet propagated to S3 bucket policy
|
|
26004
|
+
"Invalid principal in policy",
|
|
26005
|
+
// S3 bucket creation/deletion still in progress
|
|
26006
|
+
"conflicting conditional operation",
|
|
26007
|
+
// Secrets Manager: ForceDeleteWithoutRecovery may take a moment to propagate
|
|
26008
|
+
"scheduled for deletion",
|
|
26009
|
+
// DynamoDB Streams / Kinesis: IAM role not yet propagated
|
|
26010
|
+
"Cannot access stream",
|
|
26011
|
+
"Please ensure the role can perform",
|
|
26012
|
+
// KMS: IAM role not yet propagated for CreateGrant
|
|
26013
|
+
"KMS key is invalid for CreateGrant",
|
|
26014
|
+
// CloudWatch Logs SubscriptionFilter: Kinesis stream eventual consistency
|
|
26015
|
+
// or SubscriptionFilter role propagation. CW Logs probes the destination
|
|
26016
|
+
// by delivering a test message; if the stream is freshly ACTIVE or the
|
|
26017
|
+
// assumed role hasn't propagated, the probe fails with "Invalid request".
|
|
26018
|
+
"Could not deliver test message"
|
|
26019
|
+
];
|
|
26020
|
+
var RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
|
|
26021
|
+
function isRetryableTransientError(error, message) {
|
|
26022
|
+
const metadata = error.$metadata;
|
|
26023
|
+
const statusCode = metadata?.httpStatusCode;
|
|
26024
|
+
if (statusCode !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(statusCode))
|
|
26025
|
+
return true;
|
|
26026
|
+
const cause = error.cause;
|
|
26027
|
+
const causeStatus = cause?.$metadata?.httpStatusCode;
|
|
26028
|
+
if (causeStatus !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(causeStatus))
|
|
26029
|
+
return true;
|
|
26030
|
+
return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
26031
|
+
}
|
|
26032
|
+
|
|
25842
26033
|
// src/deployment/deploy-engine.ts
|
|
25843
26034
|
var InterruptedError = class extends Error {
|
|
25844
26035
|
constructor() {
|
|
@@ -25874,11 +26065,15 @@ var DeployEngine = class {
|
|
|
25874
26065
|
this.logger.debug(`Starting deployment for stack: ${stackName}`);
|
|
25875
26066
|
setCurrentStackName(stackName);
|
|
25876
26067
|
await this.lockManager.acquireLockWithRetry(stackName, void 0, "deploy");
|
|
26068
|
+
const renderer = getLiveRenderer();
|
|
26069
|
+
renderer.start();
|
|
25877
26070
|
this.interrupted = false;
|
|
25878
26071
|
const sigintHandler = () => {
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
|
|
26072
|
+
renderer.printAbove(() => {
|
|
26073
|
+
process.stderr.write(
|
|
26074
|
+
"\nInterrupted \u2014 saving partial state after current operations complete...\n"
|
|
26075
|
+
);
|
|
26076
|
+
});
|
|
25882
26077
|
this.interrupted = true;
|
|
25883
26078
|
};
|
|
25884
26079
|
process.on("SIGINT", sigintHandler);
|
|
@@ -25993,6 +26188,7 @@ var DeployEngine = class {
|
|
|
25993
26188
|
durationMs
|
|
25994
26189
|
};
|
|
25995
26190
|
} finally {
|
|
26191
|
+
renderer.stop();
|
|
25996
26192
|
process.removeListener("SIGINT", sigintHandler);
|
|
25997
26193
|
try {
|
|
25998
26194
|
await this.lockManager.releaseLock(stackName);
|
|
@@ -26421,6 +26617,10 @@ var DeployEngine = class {
|
|
|
26421
26617
|
async provisionResource(logicalId, change, stateResources, stackName, template, parameterValues, conditions, counts, progress) {
|
|
26422
26618
|
const resourceType = change.resourceType;
|
|
26423
26619
|
const provider = this.providerRegistry.getProvider(resourceType);
|
|
26620
|
+
const renderer = getLiveRenderer();
|
|
26621
|
+
const needsReplacement = change.changeType === "UPDATE" && (change.propertyChanges?.some((pc) => pc.requiresReplacement) ?? false);
|
|
26622
|
+
const verb = change.changeType === "CREATE" ? "Creating" : change.changeType === "DELETE" ? "Deleting" : needsReplacement ? "Replacing" : "Updating";
|
|
26623
|
+
renderer.addTask(logicalId, `${verb} ${logicalId} (${resourceType})`);
|
|
26424
26624
|
try {
|
|
26425
26625
|
switch (change.changeType) {
|
|
26426
26626
|
case "CREATE": {
|
|
@@ -26452,6 +26652,7 @@ var DeployEngine = class {
|
|
|
26452
26652
|
if (progress)
|
|
26453
26653
|
progress.current++;
|
|
26454
26654
|
const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
26655
|
+
renderer.removeTask(logicalId);
|
|
26455
26656
|
this.logger.info(`${createPrefix}\u2705 ${logicalId} (${resourceType}) created`);
|
|
26456
26657
|
break;
|
|
26457
26658
|
}
|
|
@@ -26479,9 +26680,9 @@ var DeployEngine = class {
|
|
|
26479
26680
|
counts.skipped++;
|
|
26480
26681
|
break;
|
|
26481
26682
|
}
|
|
26482
|
-
const
|
|
26683
|
+
const needsReplacement2 = change.propertyChanges?.some((pc) => pc.requiresReplacement);
|
|
26483
26684
|
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
26484
|
-
if (
|
|
26685
|
+
if (needsReplacement2) {
|
|
26485
26686
|
const replacedProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
26486
26687
|
this.logger.info(
|
|
26487
26688
|
`Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`
|
|
@@ -26525,6 +26726,7 @@ var DeployEngine = class {
|
|
|
26525
26726
|
if (progress)
|
|
26526
26727
|
progress.current++;
|
|
26527
26728
|
const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
26729
|
+
renderer.removeTask(logicalId);
|
|
26528
26730
|
this.logger.info(`${replacePrefix}\u2705 ${logicalId} (${resourceType}) replaced`);
|
|
26529
26731
|
} else {
|
|
26530
26732
|
this.logger.debug(`Updating ${logicalId} (${resourceType})`);
|
|
@@ -26600,6 +26802,7 @@ var DeployEngine = class {
|
|
|
26600
26802
|
if (progress)
|
|
26601
26803
|
progress.current++;
|
|
26602
26804
|
const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
26805
|
+
renderer.removeTask(logicalId);
|
|
26603
26806
|
this.logger.info(`${updatePrefix}\u2705 ${logicalId} (${resourceType}) updated`);
|
|
26604
26807
|
}
|
|
26605
26808
|
break;
|
|
@@ -26645,11 +26848,13 @@ var DeployEngine = class {
|
|
|
26645
26848
|
if (progress)
|
|
26646
26849
|
progress.current++;
|
|
26647
26850
|
const deletePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
26851
|
+
renderer.removeTask(logicalId);
|
|
26648
26852
|
this.logger.info(`${deletePrefix}\u2705 ${logicalId} (${resourceType}) deleted`);
|
|
26649
26853
|
break;
|
|
26650
26854
|
}
|
|
26651
26855
|
}
|
|
26652
26856
|
} catch (error) {
|
|
26857
|
+
renderer.removeTask(logicalId);
|
|
26653
26858
|
const message = error instanceof Error ? error.message : String(error);
|
|
26654
26859
|
this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);
|
|
26655
26860
|
throw new ProvisioningError(
|
|
@@ -26659,6 +26864,8 @@ var DeployEngine = class {
|
|
|
26659
26864
|
stateResources[logicalId]?.physicalId,
|
|
26660
26865
|
error instanceof Error ? error : void 0
|
|
26661
26866
|
);
|
|
26867
|
+
} finally {
|
|
26868
|
+
renderer.removeTask(logicalId);
|
|
26662
26869
|
}
|
|
26663
26870
|
}
|
|
26664
26871
|
/**
|
|
@@ -26819,7 +27026,7 @@ var DeployEngine = class {
|
|
|
26819
27026
|
} catch (error) {
|
|
26820
27027
|
lastError = error;
|
|
26821
27028
|
const message = error instanceof Error ? error.message : String(error);
|
|
26822
|
-
const isRetryable =
|
|
27029
|
+
const isRetryable = isRetryableTransientError(error, message);
|
|
26823
27030
|
if (!isRetryable || attempt >= maxRetries) {
|
|
26824
27031
|
throw error;
|
|
26825
27032
|
}
|
|
@@ -26836,53 +27043,6 @@ var DeployEngine = class {
|
|
|
26836
27043
|
}
|
|
26837
27044
|
throw lastError;
|
|
26838
27045
|
}
|
|
26839
|
-
/**
|
|
26840
|
-
* Determine if an error is retryable (transient).
|
|
26841
|
-
* Checks HTTP status codes (429 throttle, 503 unavailable)
|
|
26842
|
-
* and IAM propagation delay message patterns.
|
|
26843
|
-
*/
|
|
26844
|
-
isRetryableError(error, message) {
|
|
26845
|
-
const metadata = error.$metadata;
|
|
26846
|
-
const statusCode = metadata?.httpStatusCode;
|
|
26847
|
-
if (statusCode === 429 || statusCode === 503)
|
|
26848
|
-
return true;
|
|
26849
|
-
const cause = error.cause;
|
|
26850
|
-
const causeStatus = cause?.$metadata?.httpStatusCode;
|
|
26851
|
-
if (causeStatus === 429 || causeStatus === 503)
|
|
26852
|
-
return true;
|
|
26853
|
-
const retryablePatterns = [
|
|
26854
|
-
"cannot be assumed",
|
|
26855
|
-
"role defined for the function",
|
|
26856
|
-
"not authorized to perform",
|
|
26857
|
-
"execution role",
|
|
26858
|
-
"trust policy",
|
|
26859
|
-
"Role validation failed",
|
|
26860
|
-
"does not have required permissions",
|
|
26861
|
-
"Trusted Entity",
|
|
26862
|
-
"currently in the following state: Pending",
|
|
26863
|
-
// DELETE dependency ordering (parallel deletion race conditions)
|
|
26864
|
-
"has dependencies and cannot be deleted",
|
|
26865
|
-
"can't be deleted since it has",
|
|
26866
|
-
"DependencyViolation",
|
|
26867
|
-
// AWS eventual consistency (dependency just created but not yet visible)
|
|
26868
|
-
// e.g., RDS DBCluster referencing a just-created DBSubnetGroup
|
|
26869
|
-
"does not exist",
|
|
26870
|
-
// AppSync schema is being created asynchronously
|
|
26871
|
-
"Schema is currently being altered",
|
|
26872
|
-
// IAM principal not yet propagated to S3 bucket policy
|
|
26873
|
-
"Invalid principal in policy",
|
|
26874
|
-
// S3 bucket creation/deletion still in progress
|
|
26875
|
-
"conflicting conditional operation",
|
|
26876
|
-
// Secrets Manager: ForceDeleteWithoutRecovery may take a moment to propagate
|
|
26877
|
-
"scheduled for deletion",
|
|
26878
|
-
// DynamoDB Streams / Kinesis: IAM role not yet propagated
|
|
26879
|
-
"Cannot access stream",
|
|
26880
|
-
"Please ensure the role can perform",
|
|
26881
|
-
// KMS: IAM role not yet propagated for CreateGrant
|
|
26882
|
-
"KMS key is invalid for CreateGrant"
|
|
26883
|
-
];
|
|
26884
|
-
return retryablePatterns.some((p) => message.includes(p));
|
|
26885
|
-
}
|
|
26886
27046
|
/**
|
|
26887
27047
|
* Resolve stack outputs from template and resource attributes
|
|
26888
27048
|
*
|
|
@@ -26922,10 +27082,43 @@ var DeployEngine = class {
|
|
|
26922
27082
|
|
|
26923
27083
|
// src/cli/commands/deploy.ts
|
|
26924
27084
|
init_aws_clients();
|
|
27085
|
+
|
|
27086
|
+
// src/cli/stack-matcher.ts
|
|
27087
|
+
function matchStacks(stacks, patterns) {
|
|
27088
|
+
if (patterns.length === 0)
|
|
27089
|
+
return [];
|
|
27090
|
+
const seen = /* @__PURE__ */ new Set();
|
|
27091
|
+
const result = [];
|
|
27092
|
+
for (const stack of stacks) {
|
|
27093
|
+
const matched = patterns.some((pattern) => stackMatchesPattern(stack, pattern));
|
|
27094
|
+
if (matched && !seen.has(stack.stackName)) {
|
|
27095
|
+
seen.add(stack.stackName);
|
|
27096
|
+
result.push(stack);
|
|
27097
|
+
}
|
|
27098
|
+
}
|
|
27099
|
+
return result;
|
|
27100
|
+
}
|
|
27101
|
+
function describeStack(stack) {
|
|
27102
|
+
if (stack.displayName && stack.displayName !== stack.stackName) {
|
|
27103
|
+
return `${stack.stackName} (${stack.displayName})`;
|
|
27104
|
+
}
|
|
27105
|
+
return stack.stackName;
|
|
27106
|
+
}
|
|
27107
|
+
function stackMatchesPattern(stack, pattern) {
|
|
27108
|
+
const target = pattern.includes("/") ? stack.displayName ?? stack.stackName : stack.stackName;
|
|
27109
|
+
if (pattern.includes("*")) {
|
|
27110
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
27111
|
+
return regex.test(target);
|
|
27112
|
+
}
|
|
27113
|
+
return target === pattern;
|
|
27114
|
+
}
|
|
27115
|
+
|
|
27116
|
+
// src/cli/commands/deploy.ts
|
|
26925
27117
|
async function deployCommand(stacks, options) {
|
|
26926
27118
|
const logger = getLogger();
|
|
26927
27119
|
if (options.verbose) {
|
|
26928
27120
|
logger.setLevel("debug");
|
|
27121
|
+
process.env["CDKD_NO_LIVE"] = "1";
|
|
26929
27122
|
}
|
|
26930
27123
|
if (!options.wait) {
|
|
26931
27124
|
process.env["CDKD_NO_WAIT"] = "true";
|
|
@@ -26983,21 +27176,17 @@ async function deployCommand(stacks, options) {
|
|
|
26983
27176
|
if (options.all) {
|
|
26984
27177
|
targetStacks = allStacks;
|
|
26985
27178
|
} else if (stackPatterns.length > 0) {
|
|
26986
|
-
targetStacks = allStacks
|
|
26987
|
-
(s) => stackPatterns.some(
|
|
26988
|
-
(pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(s.stackName) : s.stackName === pattern
|
|
26989
|
-
)
|
|
26990
|
-
);
|
|
27179
|
+
targetStacks = matchStacks(allStacks, stackPatterns);
|
|
26991
27180
|
} else if (allStacks.length === 1) {
|
|
26992
27181
|
targetStacks = allStacks;
|
|
26993
27182
|
} else {
|
|
26994
27183
|
throw new Error(
|
|
26995
|
-
`Multiple stacks found: ${allStacks.map(
|
|
27184
|
+
`Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
|
|
26996
27185
|
);
|
|
26997
27186
|
}
|
|
26998
27187
|
if (targetStacks.length === 0) {
|
|
26999
27188
|
throw new Error(
|
|
27000
|
-
stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(
|
|
27189
|
+
stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(describeStack).join(", ")}` : "No stacks found in assembly"
|
|
27001
27190
|
);
|
|
27002
27191
|
}
|
|
27003
27192
|
if (!options.exclusively) {
|
|
@@ -27158,7 +27347,10 @@ Deploying stack: ${stackInfo.stackName}${stackRegion !== baseRegion ? ` (region:
|
|
|
27158
27347
|
}
|
|
27159
27348
|
}
|
|
27160
27349
|
function createDeployCommand() {
|
|
27161
|
-
const cmd = new Command3("deploy").description("Deploy CDK app using SDK/Cloud Control API").argument(
|
|
27350
|
+
const cmd = new Command3("deploy").description("Deploy CDK app using SDK/Cloud Control API").argument(
|
|
27351
|
+
"[stacks...]",
|
|
27352
|
+
"Stack name(s) to deploy. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
|
|
27353
|
+
).option("--all", "Deploy all stacks", false).action(withErrorHandling(deployCommand));
|
|
27162
27354
|
[
|
|
27163
27355
|
...commonOptions,
|
|
27164
27356
|
...appOptions,
|
|
@@ -27269,21 +27461,17 @@ async function diffCommand(stacks, options) {
|
|
|
27269
27461
|
if (options.all) {
|
|
27270
27462
|
targetStacks = allStacks;
|
|
27271
27463
|
} else if (stackPatterns.length > 0) {
|
|
27272
|
-
targetStacks = allStacks
|
|
27273
|
-
(s) => stackPatterns.some(
|
|
27274
|
-
(pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(s.stackName) : s.stackName === pattern
|
|
27275
|
-
)
|
|
27276
|
-
);
|
|
27464
|
+
targetStacks = matchStacks(allStacks, stackPatterns);
|
|
27277
27465
|
} else if (allStacks.length === 1) {
|
|
27278
27466
|
targetStacks = allStacks;
|
|
27279
27467
|
} else {
|
|
27280
27468
|
throw new Error(
|
|
27281
|
-
`Multiple stacks found: ${allStacks.map(
|
|
27469
|
+
`Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
|
|
27282
27470
|
);
|
|
27283
27471
|
}
|
|
27284
27472
|
if (targetStacks.length === 0) {
|
|
27285
27473
|
throw new Error(
|
|
27286
|
-
stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly` : "No stacks found in assembly"
|
|
27474
|
+
stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(describeStack).join(", ")}` : "No stacks found in assembly"
|
|
27287
27475
|
);
|
|
27288
27476
|
}
|
|
27289
27477
|
const stateConfig = {
|
|
@@ -27373,7 +27561,10 @@ ${createCount} to create, ${updateCount} to update, ${deleteCount} to delete`);
|
|
|
27373
27561
|
}
|
|
27374
27562
|
}
|
|
27375
27563
|
function createDiffCommand() {
|
|
27376
|
-
const cmd = new Command4("diff").description("Show difference between current state and desired state").argument(
|
|
27564
|
+
const cmd = new Command4("diff").description("Show difference between current state and desired state").argument(
|
|
27565
|
+
"[stacks...]",
|
|
27566
|
+
"Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
|
|
27567
|
+
).option("--all", "Diff all stacks", false).action(withErrorHandling(diffCommand));
|
|
27377
27568
|
[...commonOptions, ...appOptions, ...stateOptions, ...stackOptions, ...contextOptions].forEach(
|
|
27378
27569
|
(opt) => cmd.addOption(opt)
|
|
27379
27570
|
);
|
|
@@ -27388,6 +27579,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
27388
27579
|
const logger = getLogger();
|
|
27389
27580
|
if (options.verbose) {
|
|
27390
27581
|
logger.setLevel("debug");
|
|
27582
|
+
process.env["CDKD_NO_LIVE"] = "1";
|
|
27391
27583
|
}
|
|
27392
27584
|
const region = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
27393
27585
|
const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
@@ -27415,7 +27607,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
27415
27607
|
registerAllProviders(providerRegistry);
|
|
27416
27608
|
providerRegistry.setCustomResourceResponseBucket(stateBucket);
|
|
27417
27609
|
const appCmd = options.app || resolveApp();
|
|
27418
|
-
let
|
|
27610
|
+
let appStacks = [];
|
|
27419
27611
|
if (appCmd) {
|
|
27420
27612
|
try {
|
|
27421
27613
|
const synthesizer = new Synthesizer();
|
|
@@ -27425,17 +27617,21 @@ async function destroyCommand(stackArgs, options) {
|
|
|
27425
27617
|
output: options.output || "cdk.out",
|
|
27426
27618
|
...Object.keys(context).length > 0 && { context }
|
|
27427
27619
|
});
|
|
27428
|
-
|
|
27620
|
+
appStacks = result.stacks.map((s) => ({
|
|
27621
|
+
stackName: s.stackName,
|
|
27622
|
+
displayName: s.displayName
|
|
27623
|
+
}));
|
|
27429
27624
|
} catch {
|
|
27430
27625
|
logger.debug("Could not synthesize app, falling back to state-based stack list");
|
|
27431
27626
|
}
|
|
27432
27627
|
}
|
|
27433
27628
|
const allStateStacks = await stateBackend.listStacks();
|
|
27434
27629
|
let candidateStacks;
|
|
27435
|
-
if (
|
|
27436
|
-
|
|
27630
|
+
if (appStacks.length > 0) {
|
|
27631
|
+
const stateSet = new Set(allStateStacks);
|
|
27632
|
+
candidateStacks = appStacks.filter((s) => stateSet.has(s.stackName));
|
|
27437
27633
|
} else if (stackArgs.length > 0 || options.stack || options.all) {
|
|
27438
|
-
candidateStacks = allStateStacks;
|
|
27634
|
+
candidateStacks = allStateStacks.map((name) => ({ stackName: name }));
|
|
27439
27635
|
} else {
|
|
27440
27636
|
throw new Error(
|
|
27441
27637
|
"Could not determine which stacks belong to this app. Specify stack names explicitly, use --all, or ensure --app / cdk.json is configured."
|
|
@@ -27444,21 +27640,17 @@ async function destroyCommand(stackArgs, options) {
|
|
|
27444
27640
|
const stackPatterns = stackArgs.length > 0 ? stackArgs : options.stack ? [options.stack] : [];
|
|
27445
27641
|
let stackNames;
|
|
27446
27642
|
if (options.all) {
|
|
27447
|
-
stackNames = candidateStacks;
|
|
27643
|
+
stackNames = candidateStacks.map((s) => s.stackName);
|
|
27448
27644
|
} else if (stackPatterns.length > 0) {
|
|
27449
|
-
stackNames = candidateStacks.
|
|
27450
|
-
(name) => stackPatterns.some(
|
|
27451
|
-
(pattern) => pattern.includes("*") ? new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(name) : name === pattern
|
|
27452
|
-
)
|
|
27453
|
-
);
|
|
27645
|
+
stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
|
|
27454
27646
|
} else if (candidateStacks.length === 1) {
|
|
27455
|
-
stackNames = candidateStacks;
|
|
27647
|
+
stackNames = candidateStacks.map((s) => s.stackName);
|
|
27456
27648
|
} else if (candidateStacks.length === 0) {
|
|
27457
27649
|
logger.info("No stacks found in state");
|
|
27458
27650
|
return;
|
|
27459
27651
|
} else {
|
|
27460
27652
|
throw new Error(
|
|
27461
|
-
`Multiple stacks found: ${candidateStacks.join(", ")}. Specify stack name(s) or use --all`
|
|
27653
|
+
`Multiple stacks found: ${candidateStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
|
|
27462
27654
|
);
|
|
27463
27655
|
}
|
|
27464
27656
|
if (stackNames.length === 0) {
|
|
@@ -27522,6 +27714,8 @@ Are you sure you want to destroy stack "${stackName}" and delete all ${resourceC
|
|
|
27522
27714
|
logger.info(`
|
|
27523
27715
|
Acquiring lock for stack ${stackName}...`);
|
|
27524
27716
|
await lockManager.acquireLock(stackName, "destroy");
|
|
27717
|
+
const renderer = getLiveRenderer();
|
|
27718
|
+
renderer.start();
|
|
27525
27719
|
try {
|
|
27526
27720
|
logger.info("Building dependency graph...");
|
|
27527
27721
|
const template = {
|
|
@@ -27585,6 +27779,7 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
27585
27779
|
logger.warn(`Resource ${logicalId} not found in state, skipping`);
|
|
27586
27780
|
return;
|
|
27587
27781
|
}
|
|
27782
|
+
renderer.addTask(logicalId, `Deleting ${logicalId} (${resource.resourceType})`);
|
|
27588
27783
|
try {
|
|
27589
27784
|
const provider = destroyProviderRegistry.getProvider(resource.resourceType);
|
|
27590
27785
|
let lastDeleteError;
|
|
@@ -27613,9 +27808,11 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
27613
27808
|
}
|
|
27614
27809
|
if (lastDeleteError)
|
|
27615
27810
|
throw lastDeleteError;
|
|
27811
|
+
renderer.removeTask(logicalId);
|
|
27616
27812
|
logger.info(` \u2705 ${logicalId} (${resource.resourceType}) deleted`);
|
|
27617
27813
|
deletedCount++;
|
|
27618
27814
|
} catch (error) {
|
|
27815
|
+
renderer.removeTask(logicalId);
|
|
27619
27816
|
const msg = error instanceof Error ? error.message : String(error);
|
|
27620
27817
|
if (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException")) {
|
|
27621
27818
|
logger.debug(` ${logicalId} already deleted, removing from state`);
|
|
@@ -27624,6 +27821,8 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
27624
27821
|
logger.error(` \u2717 Failed to delete ${logicalId}:`, String(error));
|
|
27625
27822
|
errorCount++;
|
|
27626
27823
|
}
|
|
27824
|
+
} finally {
|
|
27825
|
+
renderer.removeTask(logicalId);
|
|
27627
27826
|
}
|
|
27628
27827
|
});
|
|
27629
27828
|
await Promise.all(deletePromises);
|
|
@@ -27639,6 +27838,7 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
27639
27838
|
\u2713 Stack ${stackName} destroyed (${deletedCount} deleted, ${errorCount} errors)`
|
|
27640
27839
|
);
|
|
27641
27840
|
} finally {
|
|
27841
|
+
renderer.stop();
|
|
27642
27842
|
logger.debug("Releasing lock...");
|
|
27643
27843
|
await lockManager.releaseLock(stackName);
|
|
27644
27844
|
if (destroyAwsClients) {
|
|
@@ -27654,7 +27854,10 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
27654
27854
|
}
|
|
27655
27855
|
}
|
|
27656
27856
|
function createDestroyCommand() {
|
|
27657
|
-
const cmd = new Command5("destroy").description("Destroy all resources in the stack").argument(
|
|
27857
|
+
const cmd = new Command5("destroy").description("Destroy all resources in the stack").argument(
|
|
27858
|
+
"[stacks...]",
|
|
27859
|
+
"Stack name(s) to destroy. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
|
|
27860
|
+
).option("--all", "Destroy all stacks", false).action(withErrorHandling(destroyCommand));
|
|
27658
27861
|
[
|
|
27659
27862
|
...commonOptions,
|
|
27660
27863
|
...appOptions,
|
|
@@ -27768,7 +27971,7 @@ function reorderArgs(argv) {
|
|
|
27768
27971
|
}
|
|
27769
27972
|
async function main() {
|
|
27770
27973
|
const program = new Command8();
|
|
27771
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
27974
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.4.1");
|
|
27772
27975
|
program.addCommand(createBootstrapCommand());
|
|
27773
27976
|
program.addCommand(createSynthCommand());
|
|
27774
27977
|
program.addCommand(createDeployCommand());
|