@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
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -432,6 +432,141 @@ var init_aws_clients = __esm({
|
|
|
432
432
|
}
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
+
// src/utils/live-renderer.ts
|
|
436
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
437
|
+
var FRAME_INTERVAL_MS = 80;
|
|
438
|
+
var ESC = "\x1B[";
|
|
439
|
+
var LiveRenderer = class {
|
|
440
|
+
constructor(stream = process.stdout) {
|
|
441
|
+
this.stream = stream;
|
|
442
|
+
}
|
|
443
|
+
tasks = /* @__PURE__ */ new Map();
|
|
444
|
+
active = false;
|
|
445
|
+
spinnerIndex = 0;
|
|
446
|
+
interval = null;
|
|
447
|
+
linesDrawn = 0;
|
|
448
|
+
cursorHidden = false;
|
|
449
|
+
exitListener = null;
|
|
450
|
+
isActive() {
|
|
451
|
+
return this.active;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Enable the live renderer. No-op if stdout is not a TTY or if
|
|
455
|
+
* `CDKD_NO_LIVE=1`. Returns true if successfully enabled.
|
|
456
|
+
*/
|
|
457
|
+
start() {
|
|
458
|
+
if (this.active)
|
|
459
|
+
return true;
|
|
460
|
+
if (!this.stream.isTTY)
|
|
461
|
+
return false;
|
|
462
|
+
if (process.env["CDKD_NO_LIVE"] === "1")
|
|
463
|
+
return false;
|
|
464
|
+
this.active = true;
|
|
465
|
+
this.hideCursor();
|
|
466
|
+
if (!this.exitListener) {
|
|
467
|
+
this.exitListener = () => this.showCursor();
|
|
468
|
+
process.on("exit", this.exitListener);
|
|
469
|
+
}
|
|
470
|
+
this.interval = setInterval(() => this.draw(), FRAME_INTERVAL_MS);
|
|
471
|
+
if (typeof this.interval.unref === "function")
|
|
472
|
+
this.interval.unref();
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
stop() {
|
|
476
|
+
if (!this.active)
|
|
477
|
+
return;
|
|
478
|
+
if (this.interval) {
|
|
479
|
+
clearInterval(this.interval);
|
|
480
|
+
this.interval = null;
|
|
481
|
+
}
|
|
482
|
+
this.clear();
|
|
483
|
+
this.showCursor();
|
|
484
|
+
if (this.exitListener) {
|
|
485
|
+
process.removeListener("exit", this.exitListener);
|
|
486
|
+
this.exitListener = null;
|
|
487
|
+
}
|
|
488
|
+
this.tasks.clear();
|
|
489
|
+
this.active = false;
|
|
490
|
+
}
|
|
491
|
+
addTask(id, label) {
|
|
492
|
+
this.tasks.set(id, { label, startedAt: Date.now() });
|
|
493
|
+
if (this.active)
|
|
494
|
+
this.draw();
|
|
495
|
+
}
|
|
496
|
+
removeTask(id) {
|
|
497
|
+
if (!this.tasks.delete(id))
|
|
498
|
+
return;
|
|
499
|
+
if (this.active)
|
|
500
|
+
this.draw();
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Print content above the live area. Clears the live area, runs the writer,
|
|
504
|
+
* then redraws the live area. When the renderer is inactive, the writer
|
|
505
|
+
* runs directly so callers can use this unconditionally.
|
|
506
|
+
*/
|
|
507
|
+
printAbove(write) {
|
|
508
|
+
if (!this.active) {
|
|
509
|
+
write();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
this.clear();
|
|
513
|
+
write();
|
|
514
|
+
this.draw();
|
|
515
|
+
}
|
|
516
|
+
clear() {
|
|
517
|
+
if (this.linesDrawn === 0)
|
|
518
|
+
return;
|
|
519
|
+
this.stream.write("\r");
|
|
520
|
+
for (let i = 0; i < this.linesDrawn; i++) {
|
|
521
|
+
this.stream.write(`${ESC}1A${ESC}2K`);
|
|
522
|
+
}
|
|
523
|
+
this.linesDrawn = 0;
|
|
524
|
+
}
|
|
525
|
+
draw() {
|
|
526
|
+
if (!this.active)
|
|
527
|
+
return;
|
|
528
|
+
this.clear();
|
|
529
|
+
if (this.tasks.size === 0)
|
|
530
|
+
return;
|
|
531
|
+
const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length];
|
|
532
|
+
this.spinnerIndex++;
|
|
533
|
+
const cols = this.stream.columns ?? 80;
|
|
534
|
+
const lines = [];
|
|
535
|
+
for (const task of this.tasks.values()) {
|
|
536
|
+
const elapsed = ((Date.now() - task.startedAt) / 1e3).toFixed(1);
|
|
537
|
+
const raw = ` ${frame} ${task.label} (${elapsed}s)`;
|
|
538
|
+
lines.push(this.truncate(raw, cols));
|
|
539
|
+
}
|
|
540
|
+
this.stream.write(lines.join("\n") + "\n");
|
|
541
|
+
this.linesDrawn = lines.length;
|
|
542
|
+
}
|
|
543
|
+
truncate(s, maxLen) {
|
|
544
|
+
if (s.length <= maxLen)
|
|
545
|
+
return s;
|
|
546
|
+
if (maxLen <= 1)
|
|
547
|
+
return "\u2026";
|
|
548
|
+
return s.substring(0, maxLen - 1) + "\u2026";
|
|
549
|
+
}
|
|
550
|
+
hideCursor() {
|
|
551
|
+
if (this.cursorHidden)
|
|
552
|
+
return;
|
|
553
|
+
this.stream.write(`${ESC}?25l`);
|
|
554
|
+
this.cursorHidden = true;
|
|
555
|
+
}
|
|
556
|
+
showCursor() {
|
|
557
|
+
if (!this.cursorHidden)
|
|
558
|
+
return;
|
|
559
|
+
this.stream.write(`${ESC}?25h`);
|
|
560
|
+
this.cursorHidden = false;
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
var globalRenderer = null;
|
|
564
|
+
function getLiveRenderer() {
|
|
565
|
+
if (!globalRenderer)
|
|
566
|
+
globalRenderer = new LiveRenderer();
|
|
567
|
+
return globalRenderer;
|
|
568
|
+
}
|
|
569
|
+
|
|
435
570
|
// src/utils/logger.ts
|
|
436
571
|
var colors = {
|
|
437
572
|
reset: "\x1B[0m",
|
|
@@ -488,22 +623,26 @@ var ConsoleLogger = class {
|
|
|
488
623
|
}
|
|
489
624
|
debug(message, ...args) {
|
|
490
625
|
if (this.shouldLog("debug")) {
|
|
491
|
-
|
|
626
|
+
const formatted = this.formatMessage("debug", message, ...args);
|
|
627
|
+
getLiveRenderer().printAbove(() => console.debug(formatted));
|
|
492
628
|
}
|
|
493
629
|
}
|
|
494
630
|
info(message, ...args) {
|
|
495
631
|
if (this.shouldLog("info")) {
|
|
496
|
-
|
|
632
|
+
const formatted = this.formatMessage("info", message, ...args);
|
|
633
|
+
getLiveRenderer().printAbove(() => console.info(formatted));
|
|
497
634
|
}
|
|
498
635
|
}
|
|
499
636
|
warn(message, ...args) {
|
|
500
637
|
if (this.shouldLog("warn")) {
|
|
501
|
-
|
|
638
|
+
const formatted = this.formatMessage("warn", message, ...args);
|
|
639
|
+
getLiveRenderer().printAbove(() => console.warn(formatted));
|
|
502
640
|
}
|
|
503
641
|
}
|
|
504
642
|
error(message, ...args) {
|
|
505
643
|
if (this.shouldLog("error")) {
|
|
506
|
-
|
|
644
|
+
const formatted = this.formatMessage("error", message, ...args);
|
|
645
|
+
getLiveRenderer().printAbove(() => console.error(formatted));
|
|
507
646
|
}
|
|
508
647
|
}
|
|
509
648
|
/**
|
|
@@ -934,6 +1073,7 @@ var AssemblyReader = class {
|
|
|
934
1073
|
}
|
|
935
1074
|
return {
|
|
936
1075
|
stackName,
|
|
1076
|
+
displayName: artifact.displayName ?? stackName,
|
|
937
1077
|
artifactId,
|
|
938
1078
|
template,
|
|
939
1079
|
assetManifestPath,
|
|
@@ -7212,6 +7352,57 @@ var IMPLICIT_DELETE_DEPENDENCIES = {
|
|
|
7212
7352
|
]
|
|
7213
7353
|
};
|
|
7214
7354
|
|
|
7355
|
+
// src/deployment/retryable-errors.ts
|
|
7356
|
+
var RETRYABLE_ERROR_MESSAGE_PATTERNS = [
|
|
7357
|
+
// IAM propagation
|
|
7358
|
+
"cannot be assumed",
|
|
7359
|
+
"role defined for the function",
|
|
7360
|
+
"not authorized to perform",
|
|
7361
|
+
"execution role",
|
|
7362
|
+
"trust policy",
|
|
7363
|
+
"Role validation failed",
|
|
7364
|
+
"does not have required permissions",
|
|
7365
|
+
"Trusted Entity",
|
|
7366
|
+
"currently in the following state: Pending",
|
|
7367
|
+
// DELETE dependency ordering (parallel deletion race conditions)
|
|
7368
|
+
"has dependencies and cannot be deleted",
|
|
7369
|
+
"can't be deleted since it has",
|
|
7370
|
+
"DependencyViolation",
|
|
7371
|
+
// AWS eventual consistency (dependency just created but not yet visible)
|
|
7372
|
+
// e.g., RDS DBCluster referencing a just-created DBSubnetGroup
|
|
7373
|
+
"does not exist",
|
|
7374
|
+
// AppSync schema is being created asynchronously
|
|
7375
|
+
"Schema is currently being altered",
|
|
7376
|
+
// IAM principal not yet propagated to S3 bucket policy
|
|
7377
|
+
"Invalid principal in policy",
|
|
7378
|
+
// S3 bucket creation/deletion still in progress
|
|
7379
|
+
"conflicting conditional operation",
|
|
7380
|
+
// Secrets Manager: ForceDeleteWithoutRecovery may take a moment to propagate
|
|
7381
|
+
"scheduled for deletion",
|
|
7382
|
+
// DynamoDB Streams / Kinesis: IAM role not yet propagated
|
|
7383
|
+
"Cannot access stream",
|
|
7384
|
+
"Please ensure the role can perform",
|
|
7385
|
+
// KMS: IAM role not yet propagated for CreateGrant
|
|
7386
|
+
"KMS key is invalid for CreateGrant",
|
|
7387
|
+
// CloudWatch Logs SubscriptionFilter: Kinesis stream eventual consistency
|
|
7388
|
+
// or SubscriptionFilter role propagation. CW Logs probes the destination
|
|
7389
|
+
// by delivering a test message; if the stream is freshly ACTIVE or the
|
|
7390
|
+
// assumed role hasn't propagated, the probe fails with "Invalid request".
|
|
7391
|
+
"Could not deliver test message"
|
|
7392
|
+
];
|
|
7393
|
+
var RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
|
|
7394
|
+
function isRetryableTransientError(error, message) {
|
|
7395
|
+
const metadata = error.$metadata;
|
|
7396
|
+
const statusCode = metadata?.httpStatusCode;
|
|
7397
|
+
if (statusCode !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(statusCode))
|
|
7398
|
+
return true;
|
|
7399
|
+
const cause = error.cause;
|
|
7400
|
+
const causeStatus = cause?.$metadata?.httpStatusCode;
|
|
7401
|
+
if (causeStatus !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(causeStatus))
|
|
7402
|
+
return true;
|
|
7403
|
+
return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
|
|
7404
|
+
}
|
|
7405
|
+
|
|
7215
7406
|
// src/deployment/deploy-engine.ts
|
|
7216
7407
|
var InterruptedError = class extends Error {
|
|
7217
7408
|
constructor() {
|
|
@@ -7247,11 +7438,15 @@ var DeployEngine = class {
|
|
|
7247
7438
|
this.logger.debug(`Starting deployment for stack: ${stackName}`);
|
|
7248
7439
|
setCurrentStackName(stackName);
|
|
7249
7440
|
await this.lockManager.acquireLockWithRetry(stackName, void 0, "deploy");
|
|
7441
|
+
const renderer = getLiveRenderer();
|
|
7442
|
+
renderer.start();
|
|
7250
7443
|
this.interrupted = false;
|
|
7251
7444
|
const sigintHandler = () => {
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7445
|
+
renderer.printAbove(() => {
|
|
7446
|
+
process.stderr.write(
|
|
7447
|
+
"\nInterrupted \u2014 saving partial state after current operations complete...\n"
|
|
7448
|
+
);
|
|
7449
|
+
});
|
|
7255
7450
|
this.interrupted = true;
|
|
7256
7451
|
};
|
|
7257
7452
|
process.on("SIGINT", sigintHandler);
|
|
@@ -7366,6 +7561,7 @@ var DeployEngine = class {
|
|
|
7366
7561
|
durationMs
|
|
7367
7562
|
};
|
|
7368
7563
|
} finally {
|
|
7564
|
+
renderer.stop();
|
|
7369
7565
|
process.removeListener("SIGINT", sigintHandler);
|
|
7370
7566
|
try {
|
|
7371
7567
|
await this.lockManager.releaseLock(stackName);
|
|
@@ -7794,6 +7990,10 @@ var DeployEngine = class {
|
|
|
7794
7990
|
async provisionResource(logicalId, change, stateResources, stackName, template, parameterValues, conditions, counts, progress) {
|
|
7795
7991
|
const resourceType = change.resourceType;
|
|
7796
7992
|
const provider = this.providerRegistry.getProvider(resourceType);
|
|
7993
|
+
const renderer = getLiveRenderer();
|
|
7994
|
+
const needsReplacement = change.changeType === "UPDATE" && (change.propertyChanges?.some((pc) => pc.requiresReplacement) ?? false);
|
|
7995
|
+
const verb = change.changeType === "CREATE" ? "Creating" : change.changeType === "DELETE" ? "Deleting" : needsReplacement ? "Replacing" : "Updating";
|
|
7996
|
+
renderer.addTask(logicalId, `${verb} ${logicalId} (${resourceType})`);
|
|
7797
7997
|
try {
|
|
7798
7998
|
switch (change.changeType) {
|
|
7799
7999
|
case "CREATE": {
|
|
@@ -7825,6 +8025,7 @@ var DeployEngine = class {
|
|
|
7825
8025
|
if (progress)
|
|
7826
8026
|
progress.current++;
|
|
7827
8027
|
const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8028
|
+
renderer.removeTask(logicalId);
|
|
7828
8029
|
this.logger.info(`${createPrefix}\u2705 ${logicalId} (${resourceType}) created`);
|
|
7829
8030
|
break;
|
|
7830
8031
|
}
|
|
@@ -7852,9 +8053,9 @@ var DeployEngine = class {
|
|
|
7852
8053
|
counts.skipped++;
|
|
7853
8054
|
break;
|
|
7854
8055
|
}
|
|
7855
|
-
const
|
|
8056
|
+
const needsReplacement2 = change.propertyChanges?.some((pc) => pc.requiresReplacement);
|
|
7856
8057
|
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
7857
|
-
if (
|
|
8058
|
+
if (needsReplacement2) {
|
|
7858
8059
|
const replacedProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
7859
8060
|
this.logger.info(
|
|
7860
8061
|
`Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`
|
|
@@ -7898,6 +8099,7 @@ var DeployEngine = class {
|
|
|
7898
8099
|
if (progress)
|
|
7899
8100
|
progress.current++;
|
|
7900
8101
|
const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8102
|
+
renderer.removeTask(logicalId);
|
|
7901
8103
|
this.logger.info(`${replacePrefix}\u2705 ${logicalId} (${resourceType}) replaced`);
|
|
7902
8104
|
} else {
|
|
7903
8105
|
this.logger.debug(`Updating ${logicalId} (${resourceType})`);
|
|
@@ -7973,6 +8175,7 @@ var DeployEngine = class {
|
|
|
7973
8175
|
if (progress)
|
|
7974
8176
|
progress.current++;
|
|
7975
8177
|
const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8178
|
+
renderer.removeTask(logicalId);
|
|
7976
8179
|
this.logger.info(`${updatePrefix}\u2705 ${logicalId} (${resourceType}) updated`);
|
|
7977
8180
|
}
|
|
7978
8181
|
break;
|
|
@@ -8018,11 +8221,13 @@ var DeployEngine = class {
|
|
|
8018
8221
|
if (progress)
|
|
8019
8222
|
progress.current++;
|
|
8020
8223
|
const deletePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8224
|
+
renderer.removeTask(logicalId);
|
|
8021
8225
|
this.logger.info(`${deletePrefix}\u2705 ${logicalId} (${resourceType}) deleted`);
|
|
8022
8226
|
break;
|
|
8023
8227
|
}
|
|
8024
8228
|
}
|
|
8025
8229
|
} catch (error) {
|
|
8230
|
+
renderer.removeTask(logicalId);
|
|
8026
8231
|
const message = error instanceof Error ? error.message : String(error);
|
|
8027
8232
|
this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);
|
|
8028
8233
|
throw new ProvisioningError(
|
|
@@ -8032,6 +8237,8 @@ var DeployEngine = class {
|
|
|
8032
8237
|
stateResources[logicalId]?.physicalId,
|
|
8033
8238
|
error instanceof Error ? error : void 0
|
|
8034
8239
|
);
|
|
8240
|
+
} finally {
|
|
8241
|
+
renderer.removeTask(logicalId);
|
|
8035
8242
|
}
|
|
8036
8243
|
}
|
|
8037
8244
|
/**
|
|
@@ -8192,7 +8399,7 @@ var DeployEngine = class {
|
|
|
8192
8399
|
} catch (error) {
|
|
8193
8400
|
lastError = error;
|
|
8194
8401
|
const message = error instanceof Error ? error.message : String(error);
|
|
8195
|
-
const isRetryable =
|
|
8402
|
+
const isRetryable = isRetryableTransientError(error, message);
|
|
8196
8403
|
if (!isRetryable || attempt >= maxRetries) {
|
|
8197
8404
|
throw error;
|
|
8198
8405
|
}
|
|
@@ -8209,53 +8416,6 @@ var DeployEngine = class {
|
|
|
8209
8416
|
}
|
|
8210
8417
|
throw lastError;
|
|
8211
8418
|
}
|
|
8212
|
-
/**
|
|
8213
|
-
* Determine if an error is retryable (transient).
|
|
8214
|
-
* Checks HTTP status codes (429 throttle, 503 unavailable)
|
|
8215
|
-
* and IAM propagation delay message patterns.
|
|
8216
|
-
*/
|
|
8217
|
-
isRetryableError(error, message) {
|
|
8218
|
-
const metadata = error.$metadata;
|
|
8219
|
-
const statusCode = metadata?.httpStatusCode;
|
|
8220
|
-
if (statusCode === 429 || statusCode === 503)
|
|
8221
|
-
return true;
|
|
8222
|
-
const cause = error.cause;
|
|
8223
|
-
const causeStatus = cause?.$metadata?.httpStatusCode;
|
|
8224
|
-
if (causeStatus === 429 || causeStatus === 503)
|
|
8225
|
-
return true;
|
|
8226
|
-
const retryablePatterns = [
|
|
8227
|
-
"cannot be assumed",
|
|
8228
|
-
"role defined for the function",
|
|
8229
|
-
"not authorized to perform",
|
|
8230
|
-
"execution role",
|
|
8231
|
-
"trust policy",
|
|
8232
|
-
"Role validation failed",
|
|
8233
|
-
"does not have required permissions",
|
|
8234
|
-
"Trusted Entity",
|
|
8235
|
-
"currently in the following state: Pending",
|
|
8236
|
-
// DELETE dependency ordering (parallel deletion race conditions)
|
|
8237
|
-
"has dependencies and cannot be deleted",
|
|
8238
|
-
"can't be deleted since it has",
|
|
8239
|
-
"DependencyViolation",
|
|
8240
|
-
// AWS eventual consistency (dependency just created but not yet visible)
|
|
8241
|
-
// e.g., RDS DBCluster referencing a just-created DBSubnetGroup
|
|
8242
|
-
"does not exist",
|
|
8243
|
-
// AppSync schema is being created asynchronously
|
|
8244
|
-
"Schema is currently being altered",
|
|
8245
|
-
// IAM principal not yet propagated to S3 bucket policy
|
|
8246
|
-
"Invalid principal in policy",
|
|
8247
|
-
// S3 bucket creation/deletion still in progress
|
|
8248
|
-
"conflicting conditional operation",
|
|
8249
|
-
// Secrets Manager: ForceDeleteWithoutRecovery may take a moment to propagate
|
|
8250
|
-
"scheduled for deletion",
|
|
8251
|
-
// DynamoDB Streams / Kinesis: IAM role not yet propagated
|
|
8252
|
-
"Cannot access stream",
|
|
8253
|
-
"Please ensure the role can perform",
|
|
8254
|
-
// KMS: IAM role not yet propagated for CreateGrant
|
|
8255
|
-
"KMS key is invalid for CreateGrant"
|
|
8256
|
-
];
|
|
8257
|
-
return retryablePatterns.some((p) => message.includes(p));
|
|
8258
|
-
}
|
|
8259
8419
|
/**
|
|
8260
8420
|
* Resolve stack outputs from template and resource attributes
|
|
8261
8421
|
*
|