@go-to-k/cdkd 0.262.2 → 0.263.0
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 +37 -1
- package/dist/{asg-provider-D85MbFl6.js → asg-provider-DF1bV_pu.js} +2 -2
- package/dist/{asg-provider-D85MbFl6.js.map → asg-provider-DF1bV_pu.js.map} +1 -1
- package/dist/cli.js +5279 -5475
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-DbhK2y6L.js → deploy-engine-BbNhlr7X.js} +971 -235
- package/dist/deploy-engine-BbNhlr7X.js.map +1 -0
- package/dist/index.d.ts +166 -128
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DbhK2y6L.js.map +0 -1
|
@@ -615,6 +615,13 @@ var AwsClients = class {
|
|
|
615
615
|
constructor(config = {}) {
|
|
616
616
|
this.config = config;
|
|
617
617
|
}
|
|
618
|
+
get clientOptions() {
|
|
619
|
+
return {
|
|
620
|
+
...this.config.region && { region: this.config.region },
|
|
621
|
+
...this.config.profile && { profile: this.config.profile },
|
|
622
|
+
...this.config.credentials && { credentials: this.config.credentials }
|
|
623
|
+
};
|
|
624
|
+
}
|
|
618
625
|
/**
|
|
619
626
|
* Get S3 client
|
|
620
627
|
*
|
|
@@ -625,8 +632,7 @@ var AwsClients = class {
|
|
|
625
632
|
*/
|
|
626
633
|
getS3Client() {
|
|
627
634
|
if (!this.s3Client) this.s3Client = new S3Client({
|
|
628
|
-
...this.
|
|
629
|
-
...this.config.credentials && { credentials: this.config.credentials },
|
|
635
|
+
...this.clientOptions,
|
|
630
636
|
logger: {
|
|
631
637
|
debug: () => {},
|
|
632
638
|
info: () => {},
|
|
@@ -645,10 +651,7 @@ var AwsClients = class {
|
|
|
645
651
|
* 3. IAM role (if running on EC2/ECS/Lambda)
|
|
646
652
|
*/
|
|
647
653
|
getCloudControlClient() {
|
|
648
|
-
if (!this.cloudControlClient) this.cloudControlClient = new CloudControlClient({
|
|
649
|
-
...this.config.region && { region: this.config.region },
|
|
650
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
651
|
-
});
|
|
654
|
+
if (!this.cloudControlClient) this.cloudControlClient = new CloudControlClient({ ...this.clientOptions });
|
|
652
655
|
return this.cloudControlClient;
|
|
653
656
|
}
|
|
654
657
|
/**
|
|
@@ -659,8 +662,8 @@ var AwsClients = class {
|
|
|
659
662
|
*/
|
|
660
663
|
getIAMClient() {
|
|
661
664
|
if (!this.iamClient) this.iamClient = new IAMClient({
|
|
662
|
-
|
|
663
|
-
|
|
665
|
+
...this.clientOptions,
|
|
666
|
+
region: this.config.region || "us-east-1"
|
|
664
667
|
});
|
|
665
668
|
return this.iamClient;
|
|
666
669
|
}
|
|
@@ -686,10 +689,7 @@ var AwsClients = class {
|
|
|
686
689
|
* Get SQS client
|
|
687
690
|
*/
|
|
688
691
|
getSQSClient() {
|
|
689
|
-
if (!this.sqsClient) this.sqsClient = new SQSClient({
|
|
690
|
-
...this.config.region && { region: this.config.region },
|
|
691
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
692
|
-
});
|
|
692
|
+
if (!this.sqsClient) this.sqsClient = new SQSClient({ ...this.clientOptions });
|
|
693
693
|
return this.sqsClient;
|
|
694
694
|
}
|
|
695
695
|
/**
|
|
@@ -702,10 +702,7 @@ var AwsClients = class {
|
|
|
702
702
|
* Get SNS client
|
|
703
703
|
*/
|
|
704
704
|
getSNSClient() {
|
|
705
|
-
if (!this.snsClient) this.snsClient = new SNSClient({
|
|
706
|
-
...this.config.region && { region: this.config.region },
|
|
707
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
708
|
-
});
|
|
705
|
+
if (!this.snsClient) this.snsClient = new SNSClient({ ...this.clientOptions });
|
|
709
706
|
return this.snsClient;
|
|
710
707
|
}
|
|
711
708
|
/**
|
|
@@ -718,10 +715,7 @@ var AwsClients = class {
|
|
|
718
715
|
* Get Lambda client
|
|
719
716
|
*/
|
|
720
717
|
getLambdaClient() {
|
|
721
|
-
if (!this.lambdaClient) this.lambdaClient = new LambdaClient({
|
|
722
|
-
...this.config.region && { region: this.config.region },
|
|
723
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
724
|
-
});
|
|
718
|
+
if (!this.lambdaClient) this.lambdaClient = new LambdaClient({ ...this.clientOptions });
|
|
725
719
|
return this.lambdaClient;
|
|
726
720
|
}
|
|
727
721
|
/**
|
|
@@ -734,10 +728,7 @@ var AwsClients = class {
|
|
|
734
728
|
* Get EC2 client
|
|
735
729
|
*/
|
|
736
730
|
getEC2Client() {
|
|
737
|
-
if (!this.ec2Client) this.ec2Client = new EC2Client({
|
|
738
|
-
...this.config.region && { region: this.config.region },
|
|
739
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
740
|
-
});
|
|
731
|
+
if (!this.ec2Client) this.ec2Client = new EC2Client({ ...this.clientOptions });
|
|
741
732
|
return this.ec2Client;
|
|
742
733
|
}
|
|
743
734
|
/**
|
|
@@ -750,10 +741,7 @@ var AwsClients = class {
|
|
|
750
741
|
* Get STS client
|
|
751
742
|
*/
|
|
752
743
|
getSTSClient() {
|
|
753
|
-
if (!this.stsClient) this.stsClient = new STSClient({
|
|
754
|
-
...this.config.region && { region: this.config.region },
|
|
755
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
756
|
-
});
|
|
744
|
+
if (!this.stsClient) this.stsClient = new STSClient({ ...this.clientOptions });
|
|
757
745
|
return this.stsClient;
|
|
758
746
|
}
|
|
759
747
|
/**
|
|
@@ -766,10 +754,7 @@ var AwsClients = class {
|
|
|
766
754
|
* Get DynamoDB client
|
|
767
755
|
*/
|
|
768
756
|
getDynamoDBClient() {
|
|
769
|
-
if (!this.dynamoDBClient) this.dynamoDBClient = new DynamoDBClient({
|
|
770
|
-
...this.config.region && { region: this.config.region },
|
|
771
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
772
|
-
});
|
|
757
|
+
if (!this.dynamoDBClient) this.dynamoDBClient = new DynamoDBClient({ ...this.clientOptions });
|
|
773
758
|
return this.dynamoDBClient;
|
|
774
759
|
}
|
|
775
760
|
/**
|
|
@@ -782,10 +767,7 @@ var AwsClients = class {
|
|
|
782
767
|
* Get CloudFormation client
|
|
783
768
|
*/
|
|
784
769
|
getCloudFormationClient() {
|
|
785
|
-
if (!this.cloudFormationClient) this.cloudFormationClient = new CloudFormationClient({
|
|
786
|
-
...this.config.region && { region: this.config.region },
|
|
787
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
788
|
-
});
|
|
770
|
+
if (!this.cloudFormationClient) this.cloudFormationClient = new CloudFormationClient({ ...this.clientOptions });
|
|
789
771
|
return this.cloudFormationClient;
|
|
790
772
|
}
|
|
791
773
|
/**
|
|
@@ -798,10 +780,7 @@ var AwsClients = class {
|
|
|
798
780
|
* Get API Gateway client
|
|
799
781
|
*/
|
|
800
782
|
getAPIGatewayClient() {
|
|
801
|
-
if (!this.apiGatewayClient) this.apiGatewayClient = new APIGatewayClient({
|
|
802
|
-
...this.config.region && { region: this.config.region },
|
|
803
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
804
|
-
});
|
|
783
|
+
if (!this.apiGatewayClient) this.apiGatewayClient = new APIGatewayClient({ ...this.clientOptions });
|
|
805
784
|
return this.apiGatewayClient;
|
|
806
785
|
}
|
|
807
786
|
/**
|
|
@@ -814,10 +793,7 @@ var AwsClients = class {
|
|
|
814
793
|
* Get EventBridge client
|
|
815
794
|
*/
|
|
816
795
|
getEventBridgeClient() {
|
|
817
|
-
if (!this.eventBridgeClient) this.eventBridgeClient = new EventBridgeClient({
|
|
818
|
-
...this.config.region && { region: this.config.region },
|
|
819
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
820
|
-
});
|
|
796
|
+
if (!this.eventBridgeClient) this.eventBridgeClient = new EventBridgeClient({ ...this.clientOptions });
|
|
821
797
|
return this.eventBridgeClient;
|
|
822
798
|
}
|
|
823
799
|
/**
|
|
@@ -830,10 +806,7 @@ var AwsClients = class {
|
|
|
830
806
|
* Get Secrets Manager client
|
|
831
807
|
*/
|
|
832
808
|
getSecretsManagerClient() {
|
|
833
|
-
if (!this.secretsManagerClient) this.secretsManagerClient = new SecretsManagerClient({
|
|
834
|
-
...this.config.region && { region: this.config.region },
|
|
835
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
836
|
-
});
|
|
809
|
+
if (!this.secretsManagerClient) this.secretsManagerClient = new SecretsManagerClient({ ...this.clientOptions });
|
|
837
810
|
return this.secretsManagerClient;
|
|
838
811
|
}
|
|
839
812
|
/**
|
|
@@ -846,10 +819,7 @@ var AwsClients = class {
|
|
|
846
819
|
* Get SSM client
|
|
847
820
|
*/
|
|
848
821
|
getSSMClient() {
|
|
849
|
-
if (!this.ssmClient) this.ssmClient = new SSMClient({
|
|
850
|
-
...this.config.region && { region: this.config.region },
|
|
851
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
852
|
-
});
|
|
822
|
+
if (!this.ssmClient) this.ssmClient = new SSMClient({ ...this.clientOptions });
|
|
853
823
|
return this.ssmClient;
|
|
854
824
|
}
|
|
855
825
|
/**
|
|
@@ -862,10 +832,7 @@ var AwsClients = class {
|
|
|
862
832
|
* Get CloudFront client
|
|
863
833
|
*/
|
|
864
834
|
getCloudFrontClient() {
|
|
865
|
-
if (!this.cloudFrontClient) this.cloudFrontClient = new CloudFrontClient({
|
|
866
|
-
...this.config.region && { region: this.config.region },
|
|
867
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
868
|
-
});
|
|
835
|
+
if (!this.cloudFrontClient) this.cloudFrontClient = new CloudFrontClient({ ...this.clientOptions });
|
|
869
836
|
return this.cloudFrontClient;
|
|
870
837
|
}
|
|
871
838
|
/**
|
|
@@ -882,10 +849,7 @@ var AwsClients = class {
|
|
|
882
849
|
* users must place their certificate stack in `us-east-1`.
|
|
883
850
|
*/
|
|
884
851
|
getACMClient() {
|
|
885
|
-
if (!this.acmClient) this.acmClient = new ACMClient({
|
|
886
|
-
...this.config.region && { region: this.config.region },
|
|
887
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
888
|
-
});
|
|
852
|
+
if (!this.acmClient) this.acmClient = new ACMClient({ ...this.clientOptions });
|
|
889
853
|
return this.acmClient;
|
|
890
854
|
}
|
|
891
855
|
/**
|
|
@@ -904,10 +868,7 @@ var AwsClients = class {
|
|
|
904
868
|
* in the same region.
|
|
905
869
|
*/
|
|
906
870
|
getLambdaMicrovmsClient() {
|
|
907
|
-
if (!this.lambdaMicrovmsClient) this.lambdaMicrovmsClient = new LambdaMicrovmsClient({
|
|
908
|
-
...this.config.region && { region: this.config.region },
|
|
909
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
910
|
-
});
|
|
871
|
+
if (!this.lambdaMicrovmsClient) this.lambdaMicrovmsClient = new LambdaMicrovmsClient({ ...this.clientOptions });
|
|
911
872
|
return this.lambdaMicrovmsClient;
|
|
912
873
|
}
|
|
913
874
|
/**
|
|
@@ -920,10 +881,7 @@ var AwsClients = class {
|
|
|
920
881
|
* Get CloudWatch client
|
|
921
882
|
*/
|
|
922
883
|
getCloudWatchClient() {
|
|
923
|
-
if (!this.cloudWatchClient) this.cloudWatchClient = new CloudWatchClient({
|
|
924
|
-
...this.config.region && { region: this.config.region },
|
|
925
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
926
|
-
});
|
|
884
|
+
if (!this.cloudWatchClient) this.cloudWatchClient = new CloudWatchClient({ ...this.clientOptions });
|
|
927
885
|
return this.cloudWatchClient;
|
|
928
886
|
}
|
|
929
887
|
/**
|
|
@@ -936,10 +894,7 @@ var AwsClients = class {
|
|
|
936
894
|
* Get CloudWatch Logs client
|
|
937
895
|
*/
|
|
938
896
|
getCloudWatchLogsClient() {
|
|
939
|
-
if (!this.cloudWatchLogsClient) this.cloudWatchLogsClient = new CloudWatchLogsClient({
|
|
940
|
-
...this.config.region && { region: this.config.region },
|
|
941
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
942
|
-
});
|
|
897
|
+
if (!this.cloudWatchLogsClient) this.cloudWatchLogsClient = new CloudWatchLogsClient({ ...this.clientOptions });
|
|
943
898
|
return this.cloudWatchLogsClient;
|
|
944
899
|
}
|
|
945
900
|
/**
|
|
@@ -952,10 +907,7 @@ var AwsClients = class {
|
|
|
952
907
|
* Get BedrockAgentCoreControl client
|
|
953
908
|
*/
|
|
954
909
|
getBedrockAgentCoreControlClient() {
|
|
955
|
-
if (!this.bedrockAgentCoreControlClient) this.bedrockAgentCoreControlClient = new BedrockAgentCoreControlClient({
|
|
956
|
-
...this.config.region && { region: this.config.region },
|
|
957
|
-
...this.config.credentials && { credentials: this.config.credentials }
|
|
958
|
-
});
|
|
910
|
+
if (!this.bedrockAgentCoreControlClient) this.bedrockAgentCoreControlClient = new BedrockAgentCoreControlClient({ ...this.clientOptions });
|
|
959
911
|
return this.bedrockAgentCoreControlClient;
|
|
960
912
|
}
|
|
961
913
|
/**
|
|
@@ -5257,6 +5209,50 @@ function shouldRetainResource(deletionPolicy) {
|
|
|
5257
5209
|
return deletionPolicy === "Retain" || deletionPolicy === "RetainExceptOnCreate";
|
|
5258
5210
|
}
|
|
5259
5211
|
|
|
5212
|
+
//#endregion
|
|
5213
|
+
//#region src/types/rollback-journal.ts
|
|
5214
|
+
/**
|
|
5215
|
+
* Journal format version, INDEPENDENT of the state schema. An unknown value
|
|
5216
|
+
* on read is a hard error telling the user to upgrade cdkd (forward-compat
|
|
5217
|
+
* guard, mirrors state-schema handling).
|
|
5218
|
+
*/
|
|
5219
|
+
const ROLLBACK_JOURNAL_VERSION = 1;
|
|
5220
|
+
/** Thrown when a journal's `journalVersion` is newer than this binary knows. */
|
|
5221
|
+
var UnknownRollbackJournalVersionError = class extends Error {
|
|
5222
|
+
foundVersion;
|
|
5223
|
+
stackName;
|
|
5224
|
+
constructor(foundVersion, stackName) {
|
|
5225
|
+
super(`Rollback journal for '${stackName}' has journalVersion ${foundVersion}, but this cdkd only understands up to ${1}. Upgrade cdkd to roll this stack back.`);
|
|
5226
|
+
this.name = "UnknownRollbackJournalVersionError";
|
|
5227
|
+
this.foundVersion = foundVersion;
|
|
5228
|
+
this.stackName = stackName;
|
|
5229
|
+
}
|
|
5230
|
+
};
|
|
5231
|
+
/**
|
|
5232
|
+
* Parse + validate a journal body. Throws
|
|
5233
|
+
* {@link UnknownRollbackJournalVersionError} on a newer version, and a plain
|
|
5234
|
+
* Error on a structurally-invalid body.
|
|
5235
|
+
*/
|
|
5236
|
+
function parseRollbackJournal(bodyString, stackName) {
|
|
5237
|
+
let parsed;
|
|
5238
|
+
try {
|
|
5239
|
+
parsed = JSON.parse(bodyString);
|
|
5240
|
+
} catch (err) {
|
|
5241
|
+
throw new Error(`Rollback journal for '${stackName}' is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
5242
|
+
}
|
|
5243
|
+
if (typeof parsed !== "object" || parsed === null) throw new Error(`Rollback journal for '${stackName}' is malformed (not an object).`);
|
|
5244
|
+
const j = parsed;
|
|
5245
|
+
if (typeof j.journalVersion !== "number" || j.journalVersion < 1) throw new Error(`Rollback journal for '${stackName}' has an invalid 'journalVersion' (${String(j.journalVersion)}).`);
|
|
5246
|
+
if (j.journalVersion > 1) throw new UnknownRollbackJournalVersionError(j.journalVersion, stackName);
|
|
5247
|
+
if (!Array.isArray(j.segments)) throw new Error(`Rollback journal for '${stackName}' is missing a 'segments' array.`);
|
|
5248
|
+
return {
|
|
5249
|
+
journalVersion: j.journalVersion,
|
|
5250
|
+
stackName: j.stackName ?? stackName,
|
|
5251
|
+
region: j.region ?? "",
|
|
5252
|
+
segments: j.segments
|
|
5253
|
+
};
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5260
5256
|
//#endregion
|
|
5261
5257
|
//#region src/utils/bucket-region-client.ts
|
|
5262
5258
|
/**
|
|
@@ -5390,6 +5386,16 @@ var S3StateBackend = class {
|
|
|
5390
5386
|
return `${this.config.prefix}/${stackName}/state.json`;
|
|
5391
5387
|
}
|
|
5392
5388
|
/**
|
|
5389
|
+
* Get the rollback-journal S3 key — a sibling of `state.json` (issue
|
|
5390
|
+
* #1183). Only the region-scoped layout is used: journals are new objects
|
|
5391
|
+
* only ever written by journal-aware binaries, and the deploy failure path
|
|
5392
|
+
* migrates legacy-layout state before the journal write, so a legacy-key
|
|
5393
|
+
* journal can never exist.
|
|
5394
|
+
*/
|
|
5395
|
+
getRollbackJournalKey(stackName, region) {
|
|
5396
|
+
return `${this.config.prefix}/${stackName}/${region}/rollback-journal.json`;
|
|
5397
|
+
}
|
|
5398
|
+
/**
|
|
5393
5399
|
* Resolve the state bucket's actual region and, if it differs from the
|
|
5394
5400
|
* client's currently-configured region, replace the S3Client with one
|
|
5395
5401
|
* pointed at the bucket's region.
|
|
@@ -5615,6 +5621,7 @@ var S3StateBackend = class {
|
|
|
5615
5621
|
}));
|
|
5616
5622
|
this.logger.debug(`Deleted legacy state for stack: ${stackName}`);
|
|
5617
5623
|
}
|
|
5624
|
+
await this.deleteRollbackJournal(stackName, region);
|
|
5618
5625
|
this.logger.debug(`State deleted: ${stackName} (${region})`);
|
|
5619
5626
|
} catch (error) {
|
|
5620
5627
|
const normalized = normalizeAwsError(error, {
|
|
@@ -5787,6 +5794,70 @@ var S3StateBackend = class {
|
|
|
5787
5794
|
if (failures.length > 0) throw new StateError(`Failed to delete ${failures.length} object(s) from bucket '${this.config.bucket}': ${failures.join("; ")}`);
|
|
5788
5795
|
}
|
|
5789
5796
|
/**
|
|
5797
|
+
* Load the rollback journal for a stack (issue #1183). Returns `null` when
|
|
5798
|
+
* no journal exists (the common case — a journal only lives between a
|
|
5799
|
+
* failed/interrupted deploy and its `cdkd rollback`). Throws
|
|
5800
|
+
* {@link UnknownRollbackJournalVersionError} on a newer-version journal.
|
|
5801
|
+
*/
|
|
5802
|
+
async loadRollbackJournal(stackName, region) {
|
|
5803
|
+
const body = await this.getRawObject(this.getRollbackJournalKey(stackName, region));
|
|
5804
|
+
if (body === null) return null;
|
|
5805
|
+
return parseRollbackJournal(body, stackName);
|
|
5806
|
+
}
|
|
5807
|
+
/**
|
|
5808
|
+
* Append one segment to the stack's rollback journal, creating it if
|
|
5809
|
+
* absent. Existing segments are preserved (never overwritten) so
|
|
5810
|
+
* consecutive failed deploys accumulate one segment each. Every writer
|
|
5811
|
+
* holds the stack lock, so no optimistic locking is needed.
|
|
5812
|
+
*/
|
|
5813
|
+
async appendRollbackJournalSegment(stackName, region, segment) {
|
|
5814
|
+
const journal = await this.loadRollbackJournal(stackName, region) ?? {
|
|
5815
|
+
journalVersion: 1,
|
|
5816
|
+
stackName,
|
|
5817
|
+
region,
|
|
5818
|
+
segments: []
|
|
5819
|
+
};
|
|
5820
|
+
journal.segments.push(segment);
|
|
5821
|
+
await this.putRawObject(this.getRollbackJournalKey(stackName, region), JSON.stringify(journal, null, 2));
|
|
5822
|
+
}
|
|
5823
|
+
/**
|
|
5824
|
+
* Pop the newest segment off the stack's rollback journal after it has
|
|
5825
|
+
* been fully replayed. When the last segment is removed, the journal
|
|
5826
|
+
* object is deleted entirely. Returns the number of segments remaining.
|
|
5827
|
+
*/
|
|
5828
|
+
async popRollbackJournalSegment(stackName, region) {
|
|
5829
|
+
const journal = await this.loadRollbackJournal(stackName, region);
|
|
5830
|
+
if (!journal || journal.segments.length === 0) {
|
|
5831
|
+
await this.deleteRollbackJournal(stackName, region);
|
|
5832
|
+
return 0;
|
|
5833
|
+
}
|
|
5834
|
+
journal.segments.pop();
|
|
5835
|
+
if (journal.segments.length === 0) {
|
|
5836
|
+
await this.deleteRollbackJournal(stackName, region);
|
|
5837
|
+
return 0;
|
|
5838
|
+
}
|
|
5839
|
+
await this.putRawObject(this.getRollbackJournalKey(stackName, region), JSON.stringify(journal, null, 2));
|
|
5840
|
+
return journal.segments.length;
|
|
5841
|
+
}
|
|
5842
|
+
/**
|
|
5843
|
+
* Delete the stack's rollback journal object (idempotent). Called on the
|
|
5844
|
+
* deploy success path, after a clean rollback, and via {@link deleteState}
|
|
5845
|
+
* so `cdkd destroy` / `cdkd state destroy` sweep it too.
|
|
5846
|
+
*/
|
|
5847
|
+
async deleteRollbackJournal(stackName, region) {
|
|
5848
|
+
await this.ensureClientForBucket();
|
|
5849
|
+
try {
|
|
5850
|
+
await this.s3Client.send(new DeleteObjectCommand({
|
|
5851
|
+
Bucket: this.config.bucket,
|
|
5852
|
+
...await this.ownerParam(),
|
|
5853
|
+
Key: this.getRollbackJournalKey(stackName, region)
|
|
5854
|
+
}));
|
|
5855
|
+
} catch (error) {
|
|
5856
|
+
if (isNoSuchKey(error) || error.name === "NotFound") return;
|
|
5857
|
+
this.logger.warn(`Failed to delete rollback journal for '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`);
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
/**
|
|
5790
5861
|
* HeadObject probe — returns true on 200, false on NotFound. Other errors
|
|
5791
5862
|
* propagate so we don't accidentally swallow IAM denials.
|
|
5792
5863
|
*/
|
|
@@ -11169,7 +11240,7 @@ var CloudControlProvider = class {
|
|
|
11169
11240
|
this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
11170
11241
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
11171
11242
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
11172
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
11243
|
+
const { ASGProvider } = await import("./asg-provider-DF1bV_pu.js").then((n) => n.n);
|
|
11173
11244
|
await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
11174
11245
|
return;
|
|
11175
11246
|
}
|
|
@@ -16332,6 +16403,763 @@ async function withResourceDeadline(operation, opts) {
|
|
|
16332
16403
|
});
|
|
16333
16404
|
}
|
|
16334
16405
|
|
|
16406
|
+
//#endregion
|
|
16407
|
+
//#region src/deployment/rollback-executor.ts
|
|
16408
|
+
/**
|
|
16409
|
+
* True when the op recorded a replacement (old physical id differs from the
|
|
16410
|
+
* new one). The old physical resource is already gone / orphaned, so an
|
|
16411
|
+
* in-place revert is best-effort — the plan labels these explicitly.
|
|
16412
|
+
*/
|
|
16413
|
+
function isReplacementOp(op) {
|
|
16414
|
+
return op.changeType === "UPDATE" && op.previousState?.physicalId !== void 0 && op.previousState.physicalId !== op.physicalId;
|
|
16415
|
+
}
|
|
16416
|
+
function deepEqual(a, b) {
|
|
16417
|
+
if (a === b) return true;
|
|
16418
|
+
if (a == null || b == null) return a === b;
|
|
16419
|
+
if (typeof a !== typeof b) return false;
|
|
16420
|
+
if (typeof a !== "object") return false;
|
|
16421
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
16422
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
16423
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
16424
|
+
}
|
|
16425
|
+
const ao = a;
|
|
16426
|
+
const bo = b;
|
|
16427
|
+
const ak = Object.keys(ao);
|
|
16428
|
+
if (ak.length !== Object.keys(bo).length) return false;
|
|
16429
|
+
for (const k of ak) {
|
|
16430
|
+
if (!Object.prototype.hasOwnProperty.call(bo, k)) return false;
|
|
16431
|
+
if (!deepEqual(ao[k], bo[k])) return false;
|
|
16432
|
+
}
|
|
16433
|
+
return true;
|
|
16434
|
+
}
|
|
16435
|
+
/**
|
|
16436
|
+
* Classify what a single op WILL do against the current state, without
|
|
16437
|
+
* touching AWS. Pure — used both by the command's plan preview and by the
|
|
16438
|
+
* replayer (which re-derives the action to stay in lock-step with the
|
|
16439
|
+
* plan). `orphanLogicalIds` mirrors `cdk rollback --orphan`.
|
|
16440
|
+
*/
|
|
16441
|
+
function classifyRollbackOp(op, stateResources, orphanLogicalIds) {
|
|
16442
|
+
const replacement = isReplacementOp(op);
|
|
16443
|
+
if (op.changeType === "DELETE") return "unrecoverable-delete";
|
|
16444
|
+
if (orphanLogicalIds.has(op.logicalId)) return "orphan-flag";
|
|
16445
|
+
if (op.changeType === "CREATE") {
|
|
16446
|
+
const current = stateResources[op.logicalId];
|
|
16447
|
+
if (!current) return "skip-already-done";
|
|
16448
|
+
if (op.physicalId !== void 0 && current.physicalId !== op.physicalId) return "skip-mismatch";
|
|
16449
|
+
const policy = current.deletionPolicy;
|
|
16450
|
+
if (policy === "Retain" || policy === "Snapshot") return "orphan-retain";
|
|
16451
|
+
return "delete";
|
|
16452
|
+
}
|
|
16453
|
+
const current = stateResources[op.logicalId];
|
|
16454
|
+
if (!current) return "skip-absent";
|
|
16455
|
+
if (op.previousState && deepEqual(current.properties, op.previousState.properties)) {
|
|
16456
|
+
if (!replacement) return "skip-already-done";
|
|
16457
|
+
}
|
|
16458
|
+
return "revert";
|
|
16459
|
+
}
|
|
16460
|
+
/**
|
|
16461
|
+
* Build the full ordered plan for a list of ops (one segment). Mirrors the
|
|
16462
|
+
* replay order: UPDATE/DELETE first (reverse completion order), then CREATE
|
|
16463
|
+
* deletions in dependency-aware order.
|
|
16464
|
+
*/
|
|
16465
|
+
function planRollback(operations, stateResources, orphanLogicalIds = /* @__PURE__ */ new Set()) {
|
|
16466
|
+
const { createOps, otherOps } = partitionOps(operations);
|
|
16467
|
+
return [...[...otherOps].reverse(), ...sortRollbackCreates(createOps, stateResources)].map((op) => ({
|
|
16468
|
+
op,
|
|
16469
|
+
action: classifyRollbackOp(op, stateResources, orphanLogicalIds),
|
|
16470
|
+
replacement: isReplacementOp(op)
|
|
16471
|
+
}));
|
|
16472
|
+
}
|
|
16473
|
+
function partitionOps(operations) {
|
|
16474
|
+
const createOps = [];
|
|
16475
|
+
const otherOps = [];
|
|
16476
|
+
for (const op of operations) if (op.changeType === "CREATE") createOps.push(op);
|
|
16477
|
+
else otherOps.push(op);
|
|
16478
|
+
return {
|
|
16479
|
+
createOps,
|
|
16480
|
+
otherOps
|
|
16481
|
+
};
|
|
16482
|
+
}
|
|
16483
|
+
/**
|
|
16484
|
+
* Replay a list of completed operations against `stateResources` (mutated in
|
|
16485
|
+
* place), reverting each. Best-effort: a provider failure is caught, warned,
|
|
16486
|
+
* and counted; replay continues.
|
|
16487
|
+
*
|
|
16488
|
+
* - UPDATE / DELETE first (reverse completion order), then CREATE deletions
|
|
16489
|
+
* in reverse dependency order (dependents deleted before dependencies).
|
|
16490
|
+
* - `afterOp` is invoked after each op that MUTATED state (so the command can
|
|
16491
|
+
* persist state incrementally, mirroring `saveStateAfterResource`). The
|
|
16492
|
+
* in-process engine passes no `afterOp` and saves state once at the end.
|
|
16493
|
+
* - `isInterrupted` is polled between ops; when it flips true, replay stops
|
|
16494
|
+
* (the pending op is left for a re-run).
|
|
16495
|
+
*/
|
|
16496
|
+
async function replayRollback(operations, stateResources, stackName, ctx, options = {}) {
|
|
16497
|
+
const orphanLogicalIds = options.orphanLogicalIds ?? /* @__PURE__ */ new Set();
|
|
16498
|
+
const result = {
|
|
16499
|
+
failures: 0,
|
|
16500
|
+
warnings: 0,
|
|
16501
|
+
interrupted: false
|
|
16502
|
+
};
|
|
16503
|
+
if (operations.length === 0) {
|
|
16504
|
+
ctx.logger.info("No completed operations to roll back.");
|
|
16505
|
+
return result;
|
|
16506
|
+
}
|
|
16507
|
+
ctx.logger.info(`Rolling back ${operations.length} completed operation(s)...`);
|
|
16508
|
+
ctx.recordEvent?.({
|
|
16509
|
+
eventType: "ROLLBACK_STARTED",
|
|
16510
|
+
stackName
|
|
16511
|
+
});
|
|
16512
|
+
const { createOps, otherOps } = partitionOps(operations);
|
|
16513
|
+
for (let i = otherOps.length - 1; i >= 0; i--) {
|
|
16514
|
+
if (options.isInterrupted?.()) {
|
|
16515
|
+
result.interrupted = true;
|
|
16516
|
+
break;
|
|
16517
|
+
}
|
|
16518
|
+
await replaySingle(otherOps[i], stateResources, stackName, ctx, orphanLogicalIds, result, options.afterOp);
|
|
16519
|
+
}
|
|
16520
|
+
if (!result.interrupted && createOps.length > 0) {
|
|
16521
|
+
const sorted = sortRollbackCreates(createOps, stateResources);
|
|
16522
|
+
for (const op of sorted) {
|
|
16523
|
+
if (options.isInterrupted?.()) {
|
|
16524
|
+
result.interrupted = true;
|
|
16525
|
+
break;
|
|
16526
|
+
}
|
|
16527
|
+
await replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds, result, options.afterOp);
|
|
16528
|
+
}
|
|
16529
|
+
}
|
|
16530
|
+
ctx.logger.info("Rollback completed. Some resources may remain if deletion failed.");
|
|
16531
|
+
ctx.recordEvent?.({
|
|
16532
|
+
eventType: "ROLLBACK_FINISHED",
|
|
16533
|
+
stackName
|
|
16534
|
+
});
|
|
16535
|
+
return result;
|
|
16536
|
+
}
|
|
16537
|
+
async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds, result, afterOp) {
|
|
16538
|
+
const action = classifyRollbackOp(op, stateResources, orphanLogicalIds);
|
|
16539
|
+
const { logger } = ctx;
|
|
16540
|
+
try {
|
|
16541
|
+
switch (action) {
|
|
16542
|
+
case "unrecoverable-delete":
|
|
16543
|
+
logger.warn(` Rollback: Cannot restore deleted resource ${op.logicalId} (${op.resourceType}) — resource has already been deleted`);
|
|
16544
|
+
result.warnings++;
|
|
16545
|
+
return;
|
|
16546
|
+
case "skip-already-done":
|
|
16547
|
+
logger.debug(` Rollback: ${op.logicalId} already reverted, skipping`);
|
|
16548
|
+
return;
|
|
16549
|
+
case "skip-mismatch":
|
|
16550
|
+
logger.warn(` Rollback: Skipping ${op.logicalId} — its physical id changed since the failed deploy (replaced by a later attempt); manual attention may be required`);
|
|
16551
|
+
result.warnings++;
|
|
16552
|
+
return;
|
|
16553
|
+
case "skip-absent":
|
|
16554
|
+
logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource no longer in state, skipping`);
|
|
16555
|
+
result.warnings++;
|
|
16556
|
+
return;
|
|
16557
|
+
case "orphan-flag":
|
|
16558
|
+
if (op.changeType === "CREATE") {
|
|
16559
|
+
delete stateResources[op.logicalId];
|
|
16560
|
+
logger.info(` Rollback: Orphaning created resource ${op.logicalId} (--orphan)`);
|
|
16561
|
+
await afterOp?.(op.logicalId);
|
|
16562
|
+
} else logger.info(` Rollback: Leaving ${op.logicalId} at its new state (--orphan)`);
|
|
16563
|
+
return;
|
|
16564
|
+
case "orphan-retain":
|
|
16565
|
+
delete stateResources[op.logicalId];
|
|
16566
|
+
logger.info(` Rollback: Leaving ${op.logicalId} (${op.resourceType}) in AWS (DeletionPolicy: ${stateResourcesPolicyLabel(op, stateResources)}) — removed from state`);
|
|
16567
|
+
await afterOp?.(op.logicalId);
|
|
16568
|
+
ctx.recordEvent?.({
|
|
16569
|
+
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
16570
|
+
stackName,
|
|
16571
|
+
operation: "CREATE",
|
|
16572
|
+
logicalId: op.logicalId,
|
|
16573
|
+
resourceType: op.resourceType,
|
|
16574
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
16575
|
+
});
|
|
16576
|
+
return;
|
|
16577
|
+
case "delete": {
|
|
16578
|
+
if (!op.physicalId) {
|
|
16579
|
+
logger.warn(` Rollback: Cannot delete ${op.logicalId} — no physical ID recorded`);
|
|
16580
|
+
result.warnings++;
|
|
16581
|
+
return;
|
|
16582
|
+
}
|
|
16583
|
+
logger.info(` Rollback: Deleting created resource ${op.logicalId} (${op.resourceType})`);
|
|
16584
|
+
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
16585
|
+
resourceType: op.resourceType,
|
|
16586
|
+
provisionedBy: op.provisionedBy
|
|
16587
|
+
});
|
|
16588
|
+
await provider.delete(op.logicalId, op.physicalId, op.resourceType, op.properties, { expectedRegion: ctx.region });
|
|
16589
|
+
delete stateResources[op.logicalId];
|
|
16590
|
+
logger.info(` Rollback: ${op.logicalId} deleted successfully`);
|
|
16591
|
+
await afterOp?.(op.logicalId);
|
|
16592
|
+
ctx.recordEvent?.({
|
|
16593
|
+
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
16594
|
+
stackName,
|
|
16595
|
+
operation: "CREATE",
|
|
16596
|
+
logicalId: op.logicalId,
|
|
16597
|
+
resourceType: op.resourceType,
|
|
16598
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
16599
|
+
});
|
|
16600
|
+
return;
|
|
16601
|
+
}
|
|
16602
|
+
case "revert": {
|
|
16603
|
+
if (!op.previousState) {
|
|
16604
|
+
logger.warn(` Rollback: Cannot restore ${op.logicalId} — no previous state available`);
|
|
16605
|
+
result.warnings++;
|
|
16606
|
+
return;
|
|
16607
|
+
}
|
|
16608
|
+
const current = stateResources[op.logicalId];
|
|
16609
|
+
if (!current) {
|
|
16610
|
+
logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource not found in current state`);
|
|
16611
|
+
result.warnings++;
|
|
16612
|
+
return;
|
|
16613
|
+
}
|
|
16614
|
+
logger.info(` Rollback: Restoring ${op.logicalId} (${op.resourceType}) to previous state`);
|
|
16615
|
+
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
16616
|
+
resourceType: op.resourceType,
|
|
16617
|
+
provisionedBy: op.provisionedBy
|
|
16618
|
+
});
|
|
16619
|
+
await provider.update(op.logicalId, current.physicalId, op.resourceType, op.previousState.properties, current.properties);
|
|
16620
|
+
stateResources[op.logicalId] = op.previousState;
|
|
16621
|
+
logger.info(` Rollback: ${op.logicalId} restored successfully`);
|
|
16622
|
+
await afterOp?.(op.logicalId);
|
|
16623
|
+
ctx.recordEvent?.({
|
|
16624
|
+
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
16625
|
+
stackName,
|
|
16626
|
+
operation: "UPDATE",
|
|
16627
|
+
logicalId: op.logicalId,
|
|
16628
|
+
resourceType: op.resourceType,
|
|
16629
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
16630
|
+
});
|
|
16631
|
+
return;
|
|
16632
|
+
}
|
|
16633
|
+
}
|
|
16634
|
+
} catch (rollbackError) {
|
|
16635
|
+
logger.warn(` Rollback failed for ${op.logicalId} (${op.changeType}): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
16636
|
+
logger.warn(" Continuing with remaining rollback operations...");
|
|
16637
|
+
result.failures++;
|
|
16638
|
+
ctx.recordEvent?.({
|
|
16639
|
+
eventType: "ROLLBACK_RESOURCE_FAILED",
|
|
16640
|
+
stackName,
|
|
16641
|
+
operation: op.changeType,
|
|
16642
|
+
logicalId: op.logicalId,
|
|
16643
|
+
resourceType: op.resourceType,
|
|
16644
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy },
|
|
16645
|
+
error: extractDeploymentEventError(rollbackError)
|
|
16646
|
+
});
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
16649
|
+
function stateResourcesPolicyLabel(op, stateResources) {
|
|
16650
|
+
return stateResources[op.logicalId]?.deletionPolicy ?? "Retain";
|
|
16651
|
+
}
|
|
16652
|
+
/**
|
|
16653
|
+
* Sort CREATE rollback operations so that resources depending on others are
|
|
16654
|
+
* deleted first (reverse dependency order), using state dependencies. Same
|
|
16655
|
+
* algorithm as the pre-extraction `DeployEngine.sortRollbackCreates`.
|
|
16656
|
+
*/
|
|
16657
|
+
function sortRollbackCreates(createOps, stateResources, logger) {
|
|
16658
|
+
const opMap = /* @__PURE__ */ new Map();
|
|
16659
|
+
const deleteIds = /* @__PURE__ */ new Set();
|
|
16660
|
+
for (const op of createOps) {
|
|
16661
|
+
opMap.set(op.logicalId, op);
|
|
16662
|
+
deleteIds.add(op.logicalId);
|
|
16663
|
+
}
|
|
16664
|
+
const dependedBy = /* @__PURE__ */ new Map();
|
|
16665
|
+
for (const id of deleteIds) if (!dependedBy.has(id)) dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
16666
|
+
for (const id of deleteIds) {
|
|
16667
|
+
const resource = stateResources[id];
|
|
16668
|
+
if (!resource?.dependencies) continue;
|
|
16669
|
+
for (const dep of resource.dependencies) {
|
|
16670
|
+
if (!deleteIds.has(dep)) continue;
|
|
16671
|
+
if (!dependedBy.has(dep)) dependedBy.set(dep, /* @__PURE__ */ new Set());
|
|
16672
|
+
dependedBy.get(dep).add(id);
|
|
16673
|
+
}
|
|
16674
|
+
}
|
|
16675
|
+
const sorted = [];
|
|
16676
|
+
let remaining = new Set(deleteIds);
|
|
16677
|
+
while (remaining.size > 0) {
|
|
16678
|
+
const level = [];
|
|
16679
|
+
for (const id of remaining) {
|
|
16680
|
+
const dependents = dependedBy.get(id);
|
|
16681
|
+
if (!(dependents ? [...dependents].some((d) => remaining.has(d)) : false)) level.push(id);
|
|
16682
|
+
}
|
|
16683
|
+
if (level.length === 0) {
|
|
16684
|
+
logger?.warn(`Circular dependency detected in rollback order, processing remaining ${remaining.size} resources`);
|
|
16685
|
+
for (const id of remaining) {
|
|
16686
|
+
const op = opMap.get(id);
|
|
16687
|
+
if (op) sorted.push(op);
|
|
16688
|
+
}
|
|
16689
|
+
break;
|
|
16690
|
+
}
|
|
16691
|
+
for (const id of level) {
|
|
16692
|
+
const op = opMap.get(id);
|
|
16693
|
+
if (op) sorted.push(op);
|
|
16694
|
+
}
|
|
16695
|
+
remaining = new Set([...remaining].filter((id) => !level.includes(id)));
|
|
16696
|
+
}
|
|
16697
|
+
logger?.debug(`Rollback CREATE deletion order: ${sorted.map((op) => op.logicalId).join(" → ")}`);
|
|
16698
|
+
return sorted;
|
|
16699
|
+
}
|
|
16700
|
+
|
|
16701
|
+
//#endregion
|
|
16702
|
+
//#region src/state/deployment-events-store.ts
|
|
16703
|
+
/**
|
|
16704
|
+
* Deployment-events store (issue #808) — persists the structured
|
|
16705
|
+
* deployment events emitted by the deploy engine / destroy runner as
|
|
16706
|
+
* JSONL to the state bucket, plus a small per-stack run index:
|
|
16707
|
+
*
|
|
16708
|
+
* - Events: `{prefix}/{stackName}/{region}/deployments/{runId}.jsonl`
|
|
16709
|
+
* - Index: `{prefix}/{stackName}/{region}/deployments/index.json`
|
|
16710
|
+
*
|
|
16711
|
+
* Design constraints (all load-bearing):
|
|
16712
|
+
*
|
|
16713
|
+
* - **Best-effort, never blocking**: `record()` is synchronous and only
|
|
16714
|
+
* buffers in memory; flushes run asynchronously (debounced timer +
|
|
16715
|
+
* size threshold) and are serialized on a write chain. A failed S3
|
|
16716
|
+
* write warns once and degrades to debug-level afterwards — it can
|
|
16717
|
+
* NEVER fail or block the deploy/destroy itself.
|
|
16718
|
+
* - **No locking**: each run writes to its own unique `{runId}.jsonl`
|
|
16719
|
+
* key (no concurrent writer by construction). `index.json` is
|
|
16720
|
+
* last-writer-wins — acceptable for a derived view; the `.jsonl`
|
|
16721
|
+
* files are the source of truth and `cdkd events` can read a run
|
|
16722
|
+
* directly by id even if the index lost the race.
|
|
16723
|
+
* - **No resource properties** in events — errors + metadata only
|
|
16724
|
+
* (properties may contain secrets and already live in state.json).
|
|
16725
|
+
* - **Separate keys from state.json** — no state schema bump; event
|
|
16726
|
+
* files survive `cdkd destroy` (state deletion does not touch
|
|
16727
|
+
* `deployments/`), preserving post-mortem context.
|
|
16728
|
+
* - **Bounded growth** (issue #885): `finalize()` prunes `{runId}.jsonl`
|
|
16729
|
+
* streams that fell out of the index window so the `deployments/`
|
|
16730
|
+
* prefix stays bounded; `cdkd events prune` (via
|
|
16731
|
+
* {@link DeploymentEventsReader.pruneRuns}) is the explicit purge.
|
|
16732
|
+
*/
|
|
16733
|
+
/** Max runs retained in `deployments/index.json` (newest first). */
|
|
16734
|
+
const DEPLOYMENT_EVENTS_MAX_INDEX_RUNS = 20;
|
|
16735
|
+
/** Debounce window between buffered events and the async S3 flush. */
|
|
16736
|
+
const FLUSH_INTERVAL_MS = 2e3;
|
|
16737
|
+
/** Flush immediately once this many events are buffered. */
|
|
16738
|
+
const FLUSH_EVENT_THRESHOLD = 50;
|
|
16739
|
+
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
16740
|
+
function getCdkdVersion() {
|
|
16741
|
+
return "0.263.0";
|
|
16742
|
+
}
|
|
16743
|
+
/**
|
|
16744
|
+
* Generate a time-sortable unique run id, e.g.
|
|
16745
|
+
* `20260613T012345678Z-1a2b3c4d`. The timestamp prefix keeps S3 listings
|
|
16746
|
+
* and `cdkd events` output chronologically meaningful; the random suffix
|
|
16747
|
+
* guarantees uniqueness across concurrent runs.
|
|
16748
|
+
*/
|
|
16749
|
+
function newDeploymentRunId(now = /* @__PURE__ */ new Date()) {
|
|
16750
|
+
return `${now.toISOString().replace(/[-:.]/g, "")}-${randomUUID().slice(0, 8)}`;
|
|
16751
|
+
}
|
|
16752
|
+
/** S3 key of a run's JSONL event stream. */
|
|
16753
|
+
function deploymentEventsKey(prefix, stackName, region, runId) {
|
|
16754
|
+
return `${prefix}/${stackName}/${region}/deployments/${runId}.jsonl`;
|
|
16755
|
+
}
|
|
16756
|
+
/** S3 key of a stack's run index. */
|
|
16757
|
+
function deploymentEventsIndexKey(prefix, stackName, region) {
|
|
16758
|
+
return `${prefix}/${stackName}/${region}/deployments/index.json`;
|
|
16759
|
+
}
|
|
16760
|
+
/** S3 key prefix under which a stack's `deployments/` artifacts live. */
|
|
16761
|
+
function deploymentsDirPrefix(prefix, stackName, region) {
|
|
16762
|
+
return `${prefix}/${stackName}/${region}/deployments/`;
|
|
16763
|
+
}
|
|
16764
|
+
/**
|
|
16765
|
+
* Parse the wall-clock time encoded in a run id's leading timestamp
|
|
16766
|
+
* (e.g. `20260613T012345678Z-1a2b3c4d`) back to epoch milliseconds.
|
|
16767
|
+
* Returns `null` for any id that does not start with the canonical
|
|
16768
|
+
* compact-ISO prefix — the age-based pruner treats an unparseable id as
|
|
16769
|
+
* "do not delete on age grounds", which is the safe direction.
|
|
16770
|
+
*/
|
|
16771
|
+
function runIdTimestampMs(runId) {
|
|
16772
|
+
const m = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(\d{3})Z/.exec(runId);
|
|
16773
|
+
if (!m) return null;
|
|
16774
|
+
const iso = `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}.${m[7]}Z`;
|
|
16775
|
+
const ms = Date.parse(iso);
|
|
16776
|
+
return Number.isNaN(ms) ? null : ms;
|
|
16777
|
+
}
|
|
16778
|
+
/** Extract the `{runId}` from a `.../{runId}.jsonl` event-stream key. */
|
|
16779
|
+
function runIdFromJsonlKey(key, dirPrefix) {
|
|
16780
|
+
if (!key.startsWith(dirPrefix) || !key.endsWith(".jsonl")) return null;
|
|
16781
|
+
return key.slice(dirPrefix.length, -6);
|
|
16782
|
+
}
|
|
16783
|
+
/**
|
|
16784
|
+
* Buffering JSONL writer for one deployment run. Implements the
|
|
16785
|
+
* {@link DeploymentEventRecorder} seam the deploy engine / destroy
|
|
16786
|
+
* runner emit through.
|
|
16787
|
+
*/
|
|
16788
|
+
var DeploymentEventsStore = class {
|
|
16789
|
+
logger = getLogger().child("DeploymentEvents");
|
|
16790
|
+
backend;
|
|
16791
|
+
stackName;
|
|
16792
|
+
region;
|
|
16793
|
+
command;
|
|
16794
|
+
runId;
|
|
16795
|
+
cdkdVersion;
|
|
16796
|
+
startedAt;
|
|
16797
|
+
/** All events recorded so far (the full JSONL body is re-PUT per flush —
|
|
16798
|
+
* S3 has no append, and metadata-only events are small). */
|
|
16799
|
+
events = [];
|
|
16800
|
+
/** Number of events already persisted by the last successful flush. */
|
|
16801
|
+
persistedCount = 0;
|
|
16802
|
+
flushTimer;
|
|
16803
|
+
/** Serializes S3 writes so flushes never interleave. */
|
|
16804
|
+
writeChain = Promise.resolve();
|
|
16805
|
+
warnedOnce = false;
|
|
16806
|
+
finalized = false;
|
|
16807
|
+
constructor(backend, options) {
|
|
16808
|
+
this.backend = backend;
|
|
16809
|
+
this.stackName = options.stackName;
|
|
16810
|
+
this.region = options.region;
|
|
16811
|
+
this.command = options.command;
|
|
16812
|
+
this.runId = options.runId ?? newDeploymentRunId();
|
|
16813
|
+
this.cdkdVersion = options.cdkdVersion ?? getCdkdVersion();
|
|
16814
|
+
this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16815
|
+
}
|
|
16816
|
+
/**
|
|
16817
|
+
* Buffer one event (synchronous, never throws). The timestamp is
|
|
16818
|
+
* stamped here so emitters don't need to.
|
|
16819
|
+
*/
|
|
16820
|
+
record(event) {
|
|
16821
|
+
try {
|
|
16822
|
+
if (this.finalized) return;
|
|
16823
|
+
this.events.push({
|
|
16824
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16825
|
+
...event
|
|
16826
|
+
});
|
|
16827
|
+
if (this.events.length - this.persistedCount >= FLUSH_EVENT_THRESHOLD) this.scheduleFlush(0);
|
|
16828
|
+
else this.scheduleFlush(FLUSH_INTERVAL_MS);
|
|
16829
|
+
} catch {}
|
|
16830
|
+
}
|
|
16831
|
+
/**
|
|
16832
|
+
* Final flush + index update. Called by the owner (deploy CLI per-stack
|
|
16833
|
+
* finally / destroy runner finally) after the run reaches a terminal
|
|
16834
|
+
* state. Best-effort: never throws.
|
|
16835
|
+
*/
|
|
16836
|
+
async finalize(result) {
|
|
16837
|
+
if (this.finalized) return;
|
|
16838
|
+
this.finalized = true;
|
|
16839
|
+
if (this.flushTimer) {
|
|
16840
|
+
clearTimeout(this.flushTimer);
|
|
16841
|
+
this.flushTimer = void 0;
|
|
16842
|
+
}
|
|
16843
|
+
if (this.events.length === 0) return;
|
|
16844
|
+
await this.enqueueWrite(async () => {
|
|
16845
|
+
await this.doFlush();
|
|
16846
|
+
const keptRunIds = await this.updateIndex(result);
|
|
16847
|
+
await this.pruneSupersededRunFiles(keptRunIds);
|
|
16848
|
+
});
|
|
16849
|
+
}
|
|
16850
|
+
/** Await any in-flight async flushes (used by tests). */
|
|
16851
|
+
async drain() {
|
|
16852
|
+
await this.writeChain;
|
|
16853
|
+
}
|
|
16854
|
+
scheduleFlush(delayMs) {
|
|
16855
|
+
if (this.flushTimer) {
|
|
16856
|
+
if (delayMs > 0) return;
|
|
16857
|
+
clearTimeout(this.flushTimer);
|
|
16858
|
+
}
|
|
16859
|
+
this.flushTimer = setTimeout(() => {
|
|
16860
|
+
this.flushTimer = void 0;
|
|
16861
|
+
this.enqueueWrite(() => this.doFlush());
|
|
16862
|
+
}, delayMs);
|
|
16863
|
+
this.flushTimer.unref?.();
|
|
16864
|
+
}
|
|
16865
|
+
enqueueWrite(op) {
|
|
16866
|
+
const next = this.writeChain.then(op).catch((err) => {
|
|
16867
|
+
this.warnOnce(`Failed to persist deployment events for run ${this.runId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16868
|
+
});
|
|
16869
|
+
this.writeChain = next;
|
|
16870
|
+
return next;
|
|
16871
|
+
}
|
|
16872
|
+
async doFlush() {
|
|
16873
|
+
if (this.events.length === 0 || this.events.length === this.persistedCount) return;
|
|
16874
|
+
const snapshotCount = this.events.length;
|
|
16875
|
+
const body = this.events.slice(0, snapshotCount).map((e) => JSON.stringify(e)).join("\n");
|
|
16876
|
+
await this.backend.putRawObject(deploymentEventsKey(this.backend.prefix, this.stackName, this.region, this.runId), body + "\n", "application/x-ndjson");
|
|
16877
|
+
this.persistedCount = snapshotCount;
|
|
16878
|
+
}
|
|
16879
|
+
/**
|
|
16880
|
+
* Prepend this run's summary to `deployments/index.json`, truncated to
|
|
16881
|
+
* the last {@link DEPLOYMENT_EVENTS_MAX_INDEX_RUNS} runs. Read-modify-
|
|
16882
|
+
* write WITHOUT optimistic locking — last-writer-wins (documented
|
|
16883
|
+
* trade-off; the per-run `.jsonl` files are the source of truth).
|
|
16884
|
+
*
|
|
16885
|
+
* Returns the run ids retained in the index (newest-first), which the
|
|
16886
|
+
* caller feeds to {@link pruneSupersededRunFiles} so the `.jsonl` files
|
|
16887
|
+
* stay bounded to the same window as the index.
|
|
16888
|
+
*/
|
|
16889
|
+
async updateIndex(result) {
|
|
16890
|
+
const key = deploymentEventsIndexKey(this.backend.prefix, this.stackName, this.region);
|
|
16891
|
+
let existingRuns = [];
|
|
16892
|
+
try {
|
|
16893
|
+
const raw = await this.backend.getRawObject(key);
|
|
16894
|
+
if (raw !== null) {
|
|
16895
|
+
const parsed = JSON.parse(raw);
|
|
16896
|
+
if (Array.isArray(parsed.runs)) existingRuns = parsed.runs;
|
|
16897
|
+
}
|
|
16898
|
+
} catch (err) {
|
|
16899
|
+
this.logger.debug(`Deployment-events index unreadable, rewriting: ${err instanceof Error ? err.message : String(err)}`);
|
|
16900
|
+
}
|
|
16901
|
+
const runs = [{
|
|
16902
|
+
runId: this.runId,
|
|
16903
|
+
command: this.command,
|
|
16904
|
+
cdkdVersion: this.cdkdVersion,
|
|
16905
|
+
startedAt: this.startedAt,
|
|
16906
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16907
|
+
result,
|
|
16908
|
+
eventCount: this.persistedCount
|
|
16909
|
+
}, ...existingRuns.filter((r) => r.runId !== this.runId)].slice(0, 20);
|
|
16910
|
+
const file = {
|
|
16911
|
+
indexVersion: 1,
|
|
16912
|
+
stackName: this.stackName,
|
|
16913
|
+
region: this.region,
|
|
16914
|
+
runs,
|
|
16915
|
+
lastModified: Date.now()
|
|
16916
|
+
};
|
|
16917
|
+
await this.backend.putRawObject(key, JSON.stringify(file, null, 2));
|
|
16918
|
+
return runs.map((r) => r.runId);
|
|
16919
|
+
}
|
|
16920
|
+
/**
|
|
16921
|
+
* Self-bounding prune (issue #885): delete `{runId}.jsonl` streams that
|
|
16922
|
+
* have fallen out of the index window so the `deployments/` prefix does
|
|
16923
|
+
* not grow without bound. Best-effort — runs inside the same write-chain
|
|
16924
|
+
* link as the flush + index write, so a failure here is caught + warned
|
|
16925
|
+
* once by {@link enqueueWrite} and never blocks the deploy / destroy.
|
|
16926
|
+
*
|
|
16927
|
+
* Concurrency-tolerant by construction: the cutoff is the OLDEST retained
|
|
16928
|
+
* run id, and only `.jsonl` files strictly below it are deleted. Run ids
|
|
16929
|
+
* are time-sortable, so a concurrent NEWER run's id sorts ABOVE every
|
|
16930
|
+
* retained id and is never touched. A concurrent run that STARTED earlier
|
|
16931
|
+
* but is still writing could in theory fall below the cutoff if 20+ newer
|
|
16932
|
+
* runs finalized while it ran — an extreme edge that self-heals anyway,
|
|
16933
|
+
* since the whole JSONL body is re-PUT on that run's next flush / finalize
|
|
16934
|
+
* (S3 has no append; each flush rewrites the full stream).
|
|
16935
|
+
*/
|
|
16936
|
+
async pruneSupersededRunFiles(keptRunIds) {
|
|
16937
|
+
if (keptRunIds.length < 20) return;
|
|
16938
|
+
const cutoff = keptRunIds.reduce((min, id) => id < min ? id : min, keptRunIds[0]);
|
|
16939
|
+
const dirPrefix = deploymentsDirPrefix(this.backend.prefix, this.stackName, this.region);
|
|
16940
|
+
const stale = (await this.backend.listRawKeys(dirPrefix)).filter((k) => {
|
|
16941
|
+
const runId = runIdFromJsonlKey(k, dirPrefix);
|
|
16942
|
+
return runId !== null && runId < cutoff;
|
|
16943
|
+
});
|
|
16944
|
+
if (stale.length === 0) return;
|
|
16945
|
+
await this.backend.deleteRawObjects(stale);
|
|
16946
|
+
this.logger.debug(`Pruned ${stale.length} superseded deployment-event stream(s) for ${this.stackName} (${this.region})`);
|
|
16947
|
+
}
|
|
16948
|
+
warnOnce(message) {
|
|
16949
|
+
if (this.warnedOnce) {
|
|
16950
|
+
this.logger.debug(message);
|
|
16951
|
+
return;
|
|
16952
|
+
}
|
|
16953
|
+
this.warnedOnce = true;
|
|
16954
|
+
this.logger.warn(`${message} — continuing; deployment events are best-effort and never block the run.`);
|
|
16955
|
+
}
|
|
16956
|
+
};
|
|
16957
|
+
/**
|
|
16958
|
+
* Read side for `cdkd events`: discovers regions / runs / event streams
|
|
16959
|
+
* under `{prefix}/{stackName}/{region}/deployments/`. Region discovery
|
|
16960
|
+
* deliberately does NOT rely on state.json — event files survive
|
|
16961
|
+
* `cdkd destroy`, so a destroyed stack's runs stay readable.
|
|
16962
|
+
*/
|
|
16963
|
+
var DeploymentEventsReader = class {
|
|
16964
|
+
backend;
|
|
16965
|
+
constructor(backend) {
|
|
16966
|
+
this.backend = backend;
|
|
16967
|
+
}
|
|
16968
|
+
/**
|
|
16969
|
+
* Regions that have a `deployments/` index or event stream for the
|
|
16970
|
+
* stack. Derived from the raw key listing under `{prefix}/{stackName}/`.
|
|
16971
|
+
*/
|
|
16972
|
+
async listRegions(stackName) {
|
|
16973
|
+
const prefix = `${this.backend.prefix}/${stackName}/`;
|
|
16974
|
+
const keys = await this.backend.listRawKeys(prefix);
|
|
16975
|
+
const regions = /* @__PURE__ */ new Set();
|
|
16976
|
+
for (const key of keys) {
|
|
16977
|
+
const segments = key.slice(prefix.length).split("/");
|
|
16978
|
+
if (segments.length === 3 && segments[1] === "deployments" && segments[0]) regions.add(segments[0]);
|
|
16979
|
+
}
|
|
16980
|
+
return [...regions].sort();
|
|
16981
|
+
}
|
|
16982
|
+
/**
|
|
16983
|
+
* Run summaries for `(stackName, region)`, newest first. Returns the
|
|
16984
|
+
* index file's `runs` (already newest-first); when the index is missing
|
|
16985
|
+
* or unreadable, falls back to enumerating `{runId}.jsonl` keys (sorted
|
|
16986
|
+
* descending — runIds are time-prefixed).
|
|
16987
|
+
*
|
|
16988
|
+
* In the fallback path the run's terminal result / command / version are
|
|
16989
|
+
* NOT known from the key alone, so each run's JSONL is read and its last
|
|
16990
|
+
* `RUN_FINISHED` (+ first `RUN_STARTED`) event mined for the true result,
|
|
16991
|
+
* command, version, and timestamps. A run whose JSONL has no terminal
|
|
16992
|
+
* `RUN_FINISHED` (interrupted run, or one whose index write lost the
|
|
16993
|
+
* race) is reported as `'UNKNOWN'` — it is NEVER fabricated as `'FAILED'`,
|
|
16994
|
+
* which would mislabel a successful run that merely failed to update the
|
|
16995
|
+
* derived index.
|
|
16996
|
+
*/
|
|
16997
|
+
async listRuns(stackName, region) {
|
|
16998
|
+
const key = deploymentEventsIndexKey(this.backend.prefix, stackName, region);
|
|
16999
|
+
try {
|
|
17000
|
+
const raw = await this.backend.getRawObject(key);
|
|
17001
|
+
if (raw !== null) {
|
|
17002
|
+
const parsed = JSON.parse(raw);
|
|
17003
|
+
if (Array.isArray(parsed.runs)) return parsed.runs;
|
|
17004
|
+
}
|
|
17005
|
+
} catch {}
|
|
17006
|
+
const dirPrefix = `${this.backend.prefix}/${stackName}/${region}/deployments/`;
|
|
17007
|
+
const runIds = (await this.backend.listRawKeys(dirPrefix)).filter((k) => k.endsWith(".jsonl")).map((k) => k.slice(dirPrefix.length, -6)).sort().reverse();
|
|
17008
|
+
return Promise.all(runIds.map((runId) => this.summarizeRunFromJsonl(stackName, region, runId)));
|
|
17009
|
+
}
|
|
17010
|
+
/**
|
|
17011
|
+
* Reconstruct a {@link DeploymentRunSummary} for the index-fallback path
|
|
17012
|
+
* by reading the run's JSONL. Derives the true result from the last
|
|
17013
|
+
* `RUN_FINISHED` event; absent that, returns `'UNKNOWN'` rather than
|
|
17014
|
+
* fabricating a definitive failure.
|
|
17015
|
+
*/
|
|
17016
|
+
async summarizeRunFromJsonl(stackName, region, runId) {
|
|
17017
|
+
const fallback = {
|
|
17018
|
+
runId,
|
|
17019
|
+
command: "deploy",
|
|
17020
|
+
cdkdVersion: "unknown",
|
|
17021
|
+
startedAt: "",
|
|
17022
|
+
finishedAt: "",
|
|
17023
|
+
result: "UNKNOWN",
|
|
17024
|
+
eventCount: 0
|
|
17025
|
+
};
|
|
17026
|
+
let events = null;
|
|
17027
|
+
try {
|
|
17028
|
+
events = await this.readRunEvents(stackName, region, runId);
|
|
17029
|
+
} catch {
|
|
17030
|
+
return fallback;
|
|
17031
|
+
}
|
|
17032
|
+
if (events === null || events.length === 0) return fallback;
|
|
17033
|
+
const started = events.find((e) => e.eventType === "RUN_STARTED");
|
|
17034
|
+
let finished;
|
|
17035
|
+
for (const e of events) if (e.eventType === "RUN_FINISHED") finished = e;
|
|
17036
|
+
return {
|
|
17037
|
+
runId,
|
|
17038
|
+
command: started?.command ?? fallback.command,
|
|
17039
|
+
cdkdVersion: started?.cdkdVersion ?? fallback.cdkdVersion,
|
|
17040
|
+
startedAt: started?.timestamp ?? fallback.startedAt,
|
|
17041
|
+
finishedAt: finished?.timestamp ?? fallback.finishedAt,
|
|
17042
|
+
result: finished?.result ?? "UNKNOWN",
|
|
17043
|
+
eventCount: events.length
|
|
17044
|
+
};
|
|
17045
|
+
}
|
|
17046
|
+
/**
|
|
17047
|
+
* Parse one run's JSONL event stream. Returns `null` when the run file
|
|
17048
|
+
* does not exist. Malformed lines are skipped (a torn final line from
|
|
17049
|
+
* an interrupted flush must not hide the rest of the stream).
|
|
17050
|
+
*/
|
|
17051
|
+
async readRunEvents(stackName, region, runId) {
|
|
17052
|
+
const key = deploymentEventsKey(this.backend.prefix, stackName, region, runId);
|
|
17053
|
+
const raw = await this.backend.getRawObject(key);
|
|
17054
|
+
if (raw === null) return null;
|
|
17055
|
+
const events = [];
|
|
17056
|
+
for (const line of raw.split("\n")) {
|
|
17057
|
+
const trimmed = line.trim();
|
|
17058
|
+
if (!trimmed) continue;
|
|
17059
|
+
try {
|
|
17060
|
+
events.push(JSON.parse(trimmed));
|
|
17061
|
+
} catch {}
|
|
17062
|
+
}
|
|
17063
|
+
return events;
|
|
17064
|
+
}
|
|
17065
|
+
/**
|
|
17066
|
+
* Prune a stack's recorded deployment-event runs (issue #885 — `cdkd
|
|
17067
|
+
* events prune`). Deletes `{runId}.jsonl` streams beyond a retention
|
|
17068
|
+
* window and rewrites (or removes) `index.json` to match.
|
|
17069
|
+
*
|
|
17070
|
+
* Retention semantics (see {@link DeploymentEventsPruneOptions}):
|
|
17071
|
+
* - `all` — delete every run + the index (full purge).
|
|
17072
|
+
* - `keep N` — retain the newest N runs, delete the rest.
|
|
17073
|
+
* - `olderThanMs`— delete runs whose run-id timestamp is older than the
|
|
17074
|
+
* cutoff; a run id with no parseable timestamp is kept.
|
|
17075
|
+
* - `keep` + `olderThanMs` — a run is deleted only when it is BOTH
|
|
17076
|
+
* beyond the newest-N window AND older than the cutoff
|
|
17077
|
+
* (the most conservative combination).
|
|
17078
|
+
* - none of the above — defaults to `keep DEPLOYMENT_EVENTS_MAX_INDEX_RUNS`
|
|
17079
|
+
* (matches the index window the writer self-bounds to).
|
|
17080
|
+
*
|
|
17081
|
+
* Unlike the writer's best-effort auto-prune, this is user-initiated and
|
|
17082
|
+
* surfaces errors to the caller (the command reports / exits non-zero).
|
|
17083
|
+
*/
|
|
17084
|
+
async pruneRuns(stackName, region, opts = {}) {
|
|
17085
|
+
const dirPrefix = deploymentsDirPrefix(this.backend.prefix, stackName, region);
|
|
17086
|
+
const runIdsDesc = (await this.backend.listRawKeys(dirPrefix)).map((k) => runIdFromJsonlKey(k, dirPrefix)).filter((id) => id !== null).sort().reverse();
|
|
17087
|
+
const indexKey = deploymentEventsIndexKey(this.backend.prefix, stackName, region);
|
|
17088
|
+
if (opts.all === true) {
|
|
17089
|
+
const toDelete = runIdsDesc.map((id) => deploymentEventsKey(this.backend.prefix, stackName, region, id));
|
|
17090
|
+
await this.backend.deleteRawObjects([...toDelete, indexKey]);
|
|
17091
|
+
return {
|
|
17092
|
+
deletedRunIds: runIdsDesc,
|
|
17093
|
+
remainingRunIds: [],
|
|
17094
|
+
indexDeleted: true
|
|
17095
|
+
};
|
|
17096
|
+
}
|
|
17097
|
+
const noGuards = opts.keep === void 0 && opts.olderThanMs === void 0;
|
|
17098
|
+
const keep = opts.keep ?? (noGuards ? 20 : void 0);
|
|
17099
|
+
const protectedByCount = keep === void 0 ? /* @__PURE__ */ new Set() : new Set(runIdsDesc.slice(0, keep));
|
|
17100
|
+
let candidates = runIdsDesc.filter((id) => !protectedByCount.has(id));
|
|
17101
|
+
if (opts.olderThanMs !== void 0) {
|
|
17102
|
+
const cutoff = (opts.now ?? /* @__PURE__ */ new Date()).getTime() - opts.olderThanMs;
|
|
17103
|
+
candidates = candidates.filter((id) => {
|
|
17104
|
+
const t = runIdTimestampMs(id);
|
|
17105
|
+
return t !== null && t < cutoff;
|
|
17106
|
+
});
|
|
17107
|
+
}
|
|
17108
|
+
if (candidates.length === 0) return {
|
|
17109
|
+
deletedRunIds: [],
|
|
17110
|
+
remainingRunIds: runIdsDesc,
|
|
17111
|
+
indexDeleted: false
|
|
17112
|
+
};
|
|
17113
|
+
const deletedSet = new Set(candidates);
|
|
17114
|
+
const deleteKeys = candidates.map((id) => deploymentEventsKey(this.backend.prefix, stackName, region, id));
|
|
17115
|
+
const remainingRunIds = runIdsDesc.filter((id) => !deletedSet.has(id));
|
|
17116
|
+
const indexDeleted = await this.rewriteIndexAfterPrune(indexKey, stackName, region, deletedSet, remainingRunIds.length === 0);
|
|
17117
|
+
await this.backend.deleteRawObjects(deleteKeys);
|
|
17118
|
+
return {
|
|
17119
|
+
deletedRunIds: candidates,
|
|
17120
|
+
remainingRunIds,
|
|
17121
|
+
indexDeleted
|
|
17122
|
+
};
|
|
17123
|
+
}
|
|
17124
|
+
/**
|
|
17125
|
+
* Drop the pruned run ids from `index.json`, or delete the index entirely
|
|
17126
|
+
* when no `.jsonl` streams remain. A corrupt / unreadable index is left
|
|
17127
|
+
* untouched (the `.jsonl` files are the source of truth; `cdkd events`
|
|
17128
|
+
* falls back to key enumeration). Returns whether the index was deleted.
|
|
17129
|
+
*/
|
|
17130
|
+
async rewriteIndexAfterPrune(indexKey, stackName, region, deletedRunIds, noRunsRemain) {
|
|
17131
|
+
if (noRunsRemain) {
|
|
17132
|
+
await this.backend.deleteRawObjects([indexKey]);
|
|
17133
|
+
return true;
|
|
17134
|
+
}
|
|
17135
|
+
let raw;
|
|
17136
|
+
try {
|
|
17137
|
+
raw = await this.backend.getRawObject(indexKey);
|
|
17138
|
+
} catch {
|
|
17139
|
+
return false;
|
|
17140
|
+
}
|
|
17141
|
+
if (raw === null) return false;
|
|
17142
|
+
let parsed;
|
|
17143
|
+
try {
|
|
17144
|
+
parsed = JSON.parse(raw);
|
|
17145
|
+
} catch {
|
|
17146
|
+
return false;
|
|
17147
|
+
}
|
|
17148
|
+
if (!Array.isArray(parsed.runs)) return false;
|
|
17149
|
+
const remaining = parsed.runs.filter((r) => !deletedRunIds.has(r.runId));
|
|
17150
|
+
if (remaining.length === parsed.runs.length) return false;
|
|
17151
|
+
const file = {
|
|
17152
|
+
indexVersion: 1,
|
|
17153
|
+
stackName,
|
|
17154
|
+
region,
|
|
17155
|
+
runs: remaining,
|
|
17156
|
+
lastModified: Date.now()
|
|
17157
|
+
};
|
|
17158
|
+
await this.backend.putRawObject(indexKey, JSON.stringify(file, null, 2));
|
|
17159
|
+
return false;
|
|
17160
|
+
}
|
|
17161
|
+
};
|
|
17162
|
+
|
|
16335
17163
|
//#endregion
|
|
16336
17164
|
//#region src/deployment/deploy-engine.ts
|
|
16337
17165
|
/**
|
|
@@ -16738,6 +17566,10 @@ var DeployEngine = class {
|
|
|
16738
17566
|
const currentEtag = currentStateData?.etag;
|
|
16739
17567
|
const migrationPending = currentStateData?.migrationPending ?? false;
|
|
16740
17568
|
this.logger.debug(`Loaded current state: ${Object.keys(currentState.resources).length} resources`);
|
|
17569
|
+
try {
|
|
17570
|
+
const journal = await this.stateBackend.loadRollbackJournal(stackName, this.stackRegion);
|
|
17571
|
+
if (journal && journal.segments.length > 0) this.logger.info(`A previous deploy of '${stackName}' failed or was interrupted. Run 'cdkd rollback ${stackName}' to revert it, or continue deploying to fix forward.`);
|
|
17572
|
+
} catch {}
|
|
16741
17573
|
this.kickOffAutoRefreshObservedProperties(currentState.resources);
|
|
16742
17574
|
this.logger.debug(`Template has ${Object.keys(template.Resources || {}).length} resources`);
|
|
16743
17575
|
const parameterValues = await this.resolver.resolveParameters(template, this.options.parameters);
|
|
@@ -16843,6 +17675,7 @@ var DeployEngine = class {
|
|
|
16843
17675
|
await this.drainObservedCaptures(newState.resources);
|
|
16844
17676
|
const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(newState));
|
|
16845
17677
|
this.logger.debug(`State saved (ETag: ${newEtag})`);
|
|
17678
|
+
await this.deleteRollbackJournalBestEffort(stackName);
|
|
16846
17679
|
if (this.exportIndexStore) await this.exportIndexStore.updateForStack(stackName, this.stackRegion, newState.outputs ?? {});
|
|
16847
17680
|
const durationMs = Date.now() - startTime;
|
|
16848
17681
|
const unchangedCount = this.diffCalculator.filterByType(changes, "NO_CHANGE").length + actualCounts.skipped;
|
|
@@ -17003,6 +17836,7 @@ var DeployEngine = class {
|
|
|
17003
17836
|
if (this.interrupted && this.hasPending(deleteExecutor)) throw new InterruptedError(this.interruptCause ?? "user");
|
|
17004
17837
|
}
|
|
17005
17838
|
} catch (error) {
|
|
17839
|
+
const initialDeploy = currentEtag === void 0;
|
|
17006
17840
|
try {
|
|
17007
17841
|
const preRollbackState = {
|
|
17008
17842
|
version: 8,
|
|
@@ -17025,14 +17859,20 @@ var DeployEngine = class {
|
|
|
17025
17859
|
} catch (saveError) {
|
|
17026
17860
|
this.logger.warn(`Failed to save partial state before rollback: ${saveError instanceof Error ? saveError.message : String(saveError)}`);
|
|
17027
17861
|
}
|
|
17862
|
+
let autoRollbackClean = false;
|
|
17028
17863
|
if (error instanceof InterruptedError) {
|
|
17029
|
-
this.
|
|
17864
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, "interrupted", initialDeploy);
|
|
17865
|
+
this.logger.info(`Partial state saved (${Object.keys(newResources).length} resources). Run deploy again to resume, 'cdkd rollback' to revert, or destroy to clean up.`);
|
|
17030
17866
|
throw error;
|
|
17031
17867
|
}
|
|
17032
17868
|
if (this.options.noRollback) {
|
|
17869
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, "no-rollback-failure", initialDeploy);
|
|
17033
17870
|
this.logger.warn("Deployment failed. --no-rollback is set, skipping rollback.");
|
|
17034
|
-
this.logger.warn("Partial state has been saved.
|
|
17035
|
-
} else
|
|
17871
|
+
this.logger.warn("Partial state has been saved. Run 'cdkd deploy' to resume, 'cdkd rollback' to revert, or destroy to clean up.");
|
|
17872
|
+
} else {
|
|
17873
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, "auto-rollback-started", initialDeploy);
|
|
17874
|
+
autoRollbackClean = (await this.performRollback(completedOperations, newResources, stackName)).failures === 0;
|
|
17875
|
+
}
|
|
17036
17876
|
try {
|
|
17037
17877
|
const postRollbackState = {
|
|
17038
17878
|
version: 8,
|
|
@@ -17046,6 +17886,7 @@ var DeployEngine = class {
|
|
|
17046
17886
|
};
|
|
17047
17887
|
await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(postRollbackState), { ...currentEtag !== void 0 && { expectedEtag: currentEtag } });
|
|
17048
17888
|
this.logger.debug("State saved after deployment failure");
|
|
17889
|
+
if (autoRollbackClean) await this.deleteRollbackJournalBestEffort(stackName);
|
|
17049
17890
|
} catch (saveError) {
|
|
17050
17891
|
this.logger.debug(`Retrying state save after rollback (ETag mismatch): ${saveError instanceof Error ? saveError.message : String(saveError)}`);
|
|
17051
17892
|
try {
|
|
@@ -17062,6 +17903,7 @@ var DeployEngine = class {
|
|
|
17062
17903
|
};
|
|
17063
17904
|
await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(postRollbackState), { ...freshEtag !== void 0 && { expectedEtag: freshEtag } });
|
|
17064
17905
|
this.logger.debug("State saved after deployment failure (retry succeeded)");
|
|
17906
|
+
if (autoRollbackClean) await this.deleteRollbackJournalBestEffort(stackName);
|
|
17065
17907
|
} catch (retryError) {
|
|
17066
17908
|
this.logger.warn(`Failed to save state after rollback: ${retryError instanceof Error ? retryError.message : String(retryError)}`);
|
|
17067
17909
|
}
|
|
@@ -17073,6 +17915,7 @@ var DeployEngine = class {
|
|
|
17073
17915
|
outputs = await this.resolveOutputs(template, newResources, stackName, parameterValues, conditions);
|
|
17074
17916
|
} catch (outputError) {
|
|
17075
17917
|
await this.persistStateAfterOutputFailure(stackName, currentState, newResources, currentEtag, pendingMigration);
|
|
17918
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, "no-rollback-failure", currentEtag === void 0);
|
|
17076
17919
|
throw outputError;
|
|
17077
17920
|
}
|
|
17078
17921
|
return {
|
|
@@ -17142,169 +17985,62 @@ var DeployEngine = class {
|
|
|
17142
17985
|
}
|
|
17143
17986
|
}
|
|
17144
17987
|
/**
|
|
17145
|
-
* Perform best-effort rollback of completed operations
|
|
17146
|
-
*
|
|
17147
|
-
*
|
|
17148
|
-
*
|
|
17149
|
-
* - DELETE → cannot rollback (resource already deleted), log warning
|
|
17150
|
-
*
|
|
17151
|
-
* Resources completed concurrently in the dispatcher may have dependencies
|
|
17152
|
-
* between them (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
17153
|
-
* CREATEs (deleting), dependent resources must be deleted before their
|
|
17154
|
-
* dependencies. This method sorts CREATE rollback operations using dependency
|
|
17155
|
-
* information from state, then processes UPDATE/DELETE rollbacks, and finally
|
|
17156
|
-
* processes sorted CREATE rollback deletions.
|
|
17988
|
+
* Perform best-effort rollback of completed operations (issue #1183:
|
|
17989
|
+
* extracted into `rollback-executor.ts` so the standalone `cdkd rollback`
|
|
17990
|
+
* command drives identical semantics). Thin wrapper that builds the
|
|
17991
|
+
* executor context from the engine's collaborators and delegates.
|
|
17157
17992
|
*/
|
|
17158
17993
|
async performRollback(completedOperations, stateResources, stackName) {
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
this.recordEvent({
|
|
17165
|
-
eventType: "ROLLBACK_STARTED",
|
|
17166
|
-
stackName
|
|
17167
|
-
});
|
|
17168
|
-
const createOps = [];
|
|
17169
|
-
const otherOps = [];
|
|
17170
|
-
for (const op of completedOperations) if (op.changeType === "CREATE") createOps.push(op);
|
|
17171
|
-
else otherOps.push(op);
|
|
17172
|
-
for (let i = otherOps.length - 1; i >= 0; i--) {
|
|
17173
|
-
const op = otherOps[i];
|
|
17174
|
-
await this.performSingleRollback(op, stateResources, stackName);
|
|
17175
|
-
}
|
|
17176
|
-
if (createOps.length > 0) {
|
|
17177
|
-
const sortedCreateOps = this.sortRollbackCreates(createOps, stateResources);
|
|
17178
|
-
for (const op of sortedCreateOps) await this.performSingleRollback(op, stateResources, stackName);
|
|
17179
|
-
}
|
|
17180
|
-
this.logger.info("Rollback completed. Some resources may remain if deletion failed.");
|
|
17181
|
-
this.recordEvent({
|
|
17182
|
-
eventType: "ROLLBACK_FINISHED",
|
|
17183
|
-
stackName
|
|
17184
|
-
});
|
|
17994
|
+
const result = await replayRollback(completedOperations, stateResources, stackName, this.rollbackExecutorContext());
|
|
17995
|
+
return {
|
|
17996
|
+
failures: result.failures,
|
|
17997
|
+
warnings: result.warnings
|
|
17998
|
+
};
|
|
17185
17999
|
}
|
|
17186
18000
|
/**
|
|
17187
|
-
*
|
|
17188
|
-
*
|
|
17189
|
-
*
|
|
17190
|
-
*
|
|
18001
|
+
* Best-effort rollback-journal deletion (issue #1183) used on the deploy
|
|
18002
|
+
* success path and after a clean automatic rollback. Never throws — a
|
|
18003
|
+
* failed delete only warns (the journal is advisory; the worst case is a
|
|
18004
|
+
* spurious "previous deploy failed" note on the next deploy).
|
|
17191
18005
|
*/
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
deleteIds.add(op.logicalId);
|
|
17198
|
-
}
|
|
17199
|
-
const dependedBy = /* @__PURE__ */ new Map();
|
|
17200
|
-
for (const id of deleteIds) if (!dependedBy.has(id)) dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
17201
|
-
for (const id of deleteIds) {
|
|
17202
|
-
const resource = stateResources[id];
|
|
17203
|
-
if (!resource?.dependencies) continue;
|
|
17204
|
-
for (const dep of resource.dependencies) {
|
|
17205
|
-
if (!deleteIds.has(dep)) continue;
|
|
17206
|
-
if (!dependedBy.has(dep)) dependedBy.set(dep, /* @__PURE__ */ new Set());
|
|
17207
|
-
dependedBy.get(dep).add(id);
|
|
17208
|
-
}
|
|
17209
|
-
}
|
|
17210
|
-
const sorted = [];
|
|
17211
|
-
let remaining = new Set(deleteIds);
|
|
17212
|
-
while (remaining.size > 0) {
|
|
17213
|
-
const level = [];
|
|
17214
|
-
for (const id of remaining) {
|
|
17215
|
-
const dependents = dependedBy.get(id);
|
|
17216
|
-
if (!(dependents ? [...dependents].some((d) => remaining.has(d)) : false)) level.push(id);
|
|
17217
|
-
}
|
|
17218
|
-
if (level.length === 0) {
|
|
17219
|
-
this.logger.warn(`Circular dependency detected in rollback order, processing remaining ${remaining.size} resources`);
|
|
17220
|
-
for (const id of remaining) {
|
|
17221
|
-
const op = opMap.get(id);
|
|
17222
|
-
if (op) sorted.push(op);
|
|
17223
|
-
}
|
|
17224
|
-
break;
|
|
17225
|
-
}
|
|
17226
|
-
for (const id of level) {
|
|
17227
|
-
const op = opMap.get(id);
|
|
17228
|
-
if (op) sorted.push(op);
|
|
17229
|
-
}
|
|
17230
|
-
remaining = new Set([...remaining].filter((id) => !level.includes(id)));
|
|
18006
|
+
async deleteRollbackJournalBestEffort(stackName) {
|
|
18007
|
+
try {
|
|
18008
|
+
await this.stateBackend.deleteRollbackJournal(stackName, this.stackRegion);
|
|
18009
|
+
} catch (err) {
|
|
18010
|
+
this.logger.debug(`Failed to delete rollback journal for ${stackName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
17231
18011
|
}
|
|
17232
|
-
|
|
17233
|
-
|
|
18012
|
+
}
|
|
18013
|
+
/** Build the {@link RollbackExecutorContext} from the engine's fields. */
|
|
18014
|
+
rollbackExecutorContext() {
|
|
18015
|
+
return {
|
|
18016
|
+
providerRegistry: this.providerRegistry,
|
|
18017
|
+
region: this.stackRegion,
|
|
18018
|
+
logger: this.logger,
|
|
18019
|
+
recordEvent: (event) => this.recordEvent(event)
|
|
18020
|
+
};
|
|
17234
18021
|
}
|
|
17235
18022
|
/**
|
|
17236
|
-
*
|
|
18023
|
+
* Record one rollback-journal segment (issue #1183) so the failed /
|
|
18024
|
+
* interrupted / about-to-auto-rollback deploy can be reverted later by
|
|
18025
|
+
* `cdkd rollback`. Best-effort like the partial-state save, but warns
|
|
18026
|
+
* LOUDLY on failure — the user just lost the ability to `cdkd rollback`.
|
|
17237
18027
|
*/
|
|
17238
|
-
async
|
|
18028
|
+
async writeRollbackJournalSegment(stackName, completedOperations, reason, initialDeploy) {
|
|
18029
|
+
if (completedOperations.length === 0) return;
|
|
17239
18030
|
try {
|
|
17240
|
-
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
this.logger.info(` Rollback: ${op.logicalId} deleted successfully`);
|
|
17254
|
-
this.recordEvent({
|
|
17255
|
-
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
17256
|
-
stackName,
|
|
17257
|
-
operation: "CREATE",
|
|
17258
|
-
logicalId: op.logicalId,
|
|
17259
|
-
resourceType: op.resourceType,
|
|
17260
|
-
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
17261
|
-
});
|
|
17262
|
-
break;
|
|
17263
|
-
}
|
|
17264
|
-
case "UPDATE": {
|
|
17265
|
-
if (!op.previousState) {
|
|
17266
|
-
this.logger.warn(` Rollback: Cannot restore ${op.logicalId} — no previous state available`);
|
|
17267
|
-
break;
|
|
17268
|
-
}
|
|
17269
|
-
this.logger.info(` Rollback: Restoring ${op.logicalId} (${op.resourceType}) to previous state`);
|
|
17270
|
-
const { provider } = this.providerRegistry.getProviderFor({
|
|
17271
|
-
resourceType: op.resourceType,
|
|
17272
|
-
provisionedBy: op.provisionedBy
|
|
17273
|
-
});
|
|
17274
|
-
const currentResource = stateResources[op.logicalId];
|
|
17275
|
-
if (!currentResource) {
|
|
17276
|
-
this.logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource not found in current state`);
|
|
17277
|
-
break;
|
|
17278
|
-
}
|
|
17279
|
-
await provider.update(op.logicalId, currentResource.physicalId, op.resourceType, op.previousState.properties, currentResource.properties);
|
|
17280
|
-
stateResources[op.logicalId] = op.previousState;
|
|
17281
|
-
this.logger.info(` Rollback: ${op.logicalId} restored successfully`);
|
|
17282
|
-
this.recordEvent({
|
|
17283
|
-
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
17284
|
-
stackName,
|
|
17285
|
-
operation: "UPDATE",
|
|
17286
|
-
logicalId: op.logicalId,
|
|
17287
|
-
resourceType: op.resourceType,
|
|
17288
|
-
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
17289
|
-
});
|
|
17290
|
-
break;
|
|
17291
|
-
}
|
|
17292
|
-
case "DELETE":
|
|
17293
|
-
this.logger.warn(` Rollback: Cannot restore deleted resource ${op.logicalId} (${op.resourceType}) — resource has already been deleted`);
|
|
17294
|
-
break;
|
|
17295
|
-
}
|
|
17296
|
-
} catch (rollbackError) {
|
|
17297
|
-
this.logger.warn(` Rollback failed for ${op.logicalId} (${op.changeType}): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
17298
|
-
this.logger.warn(" Continuing with remaining rollback operations...");
|
|
17299
|
-
this.recordEvent({
|
|
17300
|
-
eventType: "ROLLBACK_RESOURCE_FAILED",
|
|
17301
|
-
stackName,
|
|
17302
|
-
operation: op.changeType,
|
|
17303
|
-
logicalId: op.logicalId,
|
|
17304
|
-
resourceType: op.resourceType,
|
|
17305
|
-
...op.provisionedBy && { provisionedBy: op.provisionedBy },
|
|
17306
|
-
error: extractDeploymentEventError(rollbackError)
|
|
17307
|
-
});
|
|
18031
|
+
const segment = {
|
|
18032
|
+
...this.options.eventRecorder?.runId !== void 0 && { runId: this.options.eventRecorder.runId },
|
|
18033
|
+
timestamp: Date.now(),
|
|
18034
|
+
reason,
|
|
18035
|
+
initialDeploy,
|
|
18036
|
+
...this.options.roleArn && { roleArn: this.options.roleArn },
|
|
18037
|
+
cdkdVersion: getCdkdVersion(),
|
|
18038
|
+
operations: completedOperations
|
|
18039
|
+
};
|
|
18040
|
+
await this.stateBackend.appendRollbackJournalSegment(stackName, this.stackRegion, segment);
|
|
18041
|
+
this.logger.debug(`Rollback journal segment written (${reason})`);
|
|
18042
|
+
} catch (journalError) {
|
|
18043
|
+
this.logger.warn(`Failed to write rollback journal: ${journalError instanceof Error ? journalError.message : String(journalError)}. 'cdkd rollback' will NOT be able to revert this deploy — use 'cdkd deploy' to resume or 'cdkd destroy' to clean up.`);
|
|
17308
18044
|
}
|
|
17309
18045
|
}
|
|
17310
18046
|
/**
|
|
@@ -17909,5 +18645,5 @@ var DeployEngine = class {
|
|
|
17909
18645
|
};
|
|
17910
18646
|
|
|
17911
18647
|
//#endregion
|
|
17912
|
-
export {
|
|
17913
|
-
//# sourceMappingURL=deploy-engine-
|
|
18648
|
+
export { rewriteTemplateAssetReferences as $, StackHasActiveImportsError as $t, disableInstanceApiTermination as A, uploadCfnTemplate as At, DiffCalculator as B, CdkdError as Bt, yellow as C, resolveStateBucketWithDefaultAndSource as Ct, findActionableSilentDrops as D, CFN_TEMPLATE_URL_LIMIT as Dt, ProviderRegistry as E, CFN_TEMPLATE_BODY_LIMIT as Et, WAFv2WebACLProvider as F, AwsClients as Ft, rebuildClientForBucketRegion as G, LocalStartServiceError as Gt, TemplateParser as H, DependencyError as Ht, normalizeAwsTagsToCfn as I, getAwsClients as It, stringifyValue as J, NestedStackChildDirectDestroyError as Jt, shouldRetainResource as K, LockError as Kt, resolveExplicitPhysicalId as L, resetAwsClients as Lt, IntrinsicFunctionResolver as M, AssemblyReader as Mt, cfnRefValueFromPhysicalId as N, clearBucketRegionCache as Nt, CloudControlProvider as O, MIGRATE_TMP_PREFIX as Ot, refStateLookupFromResource as P, resolveBucketRegion as Pt, loadPublishableAssetManifest as Q, ResourceUpdateNotSupportedError as Qt, assertRegionMatch as R, setAwsClients as Rt, red as S, resolveStateBucketWithDefault as St, collectInlinePolicyNamesManagedBySiblings as T, warnDeprecatedNoPrefixCliFlag as Tt, LockManager as U, LocalInvokeBuildError as Ut, DagBuilder as V, ConfigError as Vt, S3StateBackend as W, LocalMigrateError as Wt, buildAssetRedirectMap as X, ProvisioningError as Xt, WorkGraph as Y, PartialFailureError as Yt, createAssetRedirectResolver as Z, ResourceTimeoutError as Zt, formatResourceLine as _, getLegacyStateBucketName as _t, DeploymentEventsStore as a, normalizeAwsError as an, validateAssetBucketName as at, gray as b, resolveCaptureObservedState as bt, withResourceDeadline as c, formatDockerLoginError as ct, IMPLICIT_DELETE_DEPENDENCIES as d, runDockerStreaming as dt, StackTerminationProtectionError as en, AssetModeResolver as et, computeImplicitDeleteEdges as f, AssetManifestLoader as ft, renderStatefulReason as g, getDefaultStateBucketName as gt, isStatefulRecreateTargetSync as h, synthesisStatusMessage as ht, DeploymentEventsReader as i, isCdkdError as in, parseBootstrapMarker as it, isTerminationProtectionPropagationError as j, expectedOwnerParam as jt, slowCcOperationTimeoutMs as k, findLargeInlineResources as kt, withRetry as l, getDockerCmd as lt, MULTI_REGION_RECREATE_BLOCKED_TYPES as m, Synthesizer as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, SynthesisError as nn, ensureAssetStorage as nt, planRollback as o, withErrorHandling as on, validateContainerRepoName as ot, extractDeploymentEventError as p, getDockerImageBySourceHash as pt, AssetPublisher as q, MissingCdkCliError as qt, DeployEngine as r, formatError as rn, getBootstrapMarkerKey as rt, replayRollback as s, __exportAll as sn, buildDockerImage as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, StateError as tn, BOOTSTRAP_MARKER_PREFIX as tt, isRetryableTransientError as u, runDockerForeground as ut, bold as v, resolveApp as vt, IAMRoleProvider as w, resolveUseCdkBootstrapAssets as wt, green as x, resolveSkipPrefix as xt, cyan as y, resolveAutoAssetStorage as yt, applyRoleArnIfSet as z, AssetError as zt };
|
|
18649
|
+
//# sourceMappingURL=deploy-engine-BbNhlr7X.js.map
|