@go-to-k/cdkd 0.102.1 → 0.102.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3435,15 +3435,27 @@ var S3BucketProvider = class {
3435
3435
  const region = await this.getRegion();
3436
3436
  if (region !== "us-east-1") createParams.CreateBucketConfiguration = { LocationConstraint: region };
3437
3437
  if (properties["ObjectLockEnabled"] === true || properties["ObjectLockEnabled"] === "true") createParams.ObjectLockEnabledForBucket = true;
3438
+ let createdNewBucket = false;
3438
3439
  try {
3439
3440
  await this.s3Client.send(new CreateBucketCommand(createParams));
3441
+ createdNewBucket = true;
3440
3442
  this.logger.debug(`Created S3 bucket: ${bucketName}`);
3441
3443
  } catch (createError) {
3442
3444
  if (createError instanceof Error && (createError.name === "BucketAlreadyOwnedByYou" || createError.message.includes("you already own it"))) this.logger.debug(`S3 bucket ${bucketName} already exists and is owned by you`);
3443
3445
  else throw createError;
3444
3446
  }
3445
- await this.applyConfiguration(bucketName, properties);
3446
- await this.applyAllSubConfigsForCreate(bucketName, properties);
3447
+ try {
3448
+ await this.applyConfiguration(bucketName, properties);
3449
+ await this.applyAllSubConfigsForCreate(bucketName, properties);
3450
+ } catch (innerError) {
3451
+ if (createdNewBucket) try {
3452
+ await this.s3Client.send(new DeleteBucketCommand({ Bucket: bucketName }));
3453
+ this.logger.debug(`Cleaned up partially-created S3 bucket ${logicalId} (${bucketName}) after wiring failure`);
3454
+ } catch (cleanupError) {
3455
+ this.logger.warn(`Failed to clean up partially-created S3 bucket ${logicalId} (${bucketName}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws s3api delete-bucket --bucket '${bucketName}'`);
3456
+ }
3457
+ throw innerError;
3458
+ }
3447
3459
  const attributes = await this.buildAttributes(bucketName);
3448
3460
  this.logger.debug(`Successfully created S3 bucket ${logicalId}: ${bucketName}`);
3449
3461
  return {
@@ -7810,46 +7822,56 @@ var LogsLogGroupProvider = class {
7810
7822
  const cfnTags = properties["Tags"];
7811
7823
  createParams.tags = Object.fromEntries(cfnTags.map((t) => [t.Key, t.Value]));
7812
7824
  }
7813
- await this.logsClient.send(new CreateLogGroupCommand(createParams));
7814
- const retentionInDays = properties["RetentionInDays"];
7815
- if (retentionInDays) await this.logsClient.send(new PutRetentionPolicyCommand({
7816
- logGroupName,
7817
- retentionInDays
7818
- }));
7819
- if (properties["DataProtectionPolicy"]) {
7820
- const policyDocument = typeof properties["DataProtectionPolicy"] === "string" ? properties["DataProtectionPolicy"] : JSON.stringify(properties["DataProtectionPolicy"]);
7821
- await this.logsClient.send(new PutDataProtectionPolicyCommand({
7822
- logGroupIdentifier: logGroupName,
7823
- policyDocument
7824
- }));
7825
+ let createdNewLogGroup = false;
7826
+ try {
7827
+ await this.logsClient.send(new CreateLogGroupCommand(createParams));
7828
+ createdNewLogGroup = true;
7829
+ } catch (createError) {
7830
+ if (createError instanceof ResourceAlreadyExistsException) this.logger.debug(`Log group ${logGroupName} already exists, using existing`);
7831
+ else throw createError;
7825
7832
  }
7826
- const fieldIndexPolicies = properties["FieldIndexPolicies"];
7827
- if (fieldIndexPolicies && fieldIndexPolicies.length > 0) {
7828
- if (fieldIndexPolicies.length > 1) this.logger.debug(`Log group ${logicalId} declares ${fieldIndexPolicies.length} FieldIndexPolicies; AWS only supports one log-group-level field index policy. Applying the first.`);
7829
- const first = fieldIndexPolicies[0];
7830
- const policyDocument = typeof first === "string" ? first : JSON.stringify(first);
7831
- await this.logsClient.send(new PutIndexPolicyCommand({
7833
+ try {
7834
+ const retentionInDays = properties["RetentionInDays"];
7835
+ if (retentionInDays) await this.logsClient.send(new PutRetentionPolicyCommand({
7836
+ logGroupName,
7837
+ retentionInDays
7838
+ }));
7839
+ if (properties["DataProtectionPolicy"]) {
7840
+ const policyDocument = typeof properties["DataProtectionPolicy"] === "string" ? properties["DataProtectionPolicy"] : JSON.stringify(properties["DataProtectionPolicy"]);
7841
+ await this.logsClient.send(new PutDataProtectionPolicyCommand({
7842
+ logGroupIdentifier: logGroupName,
7843
+ policyDocument
7844
+ }));
7845
+ }
7846
+ const fieldIndexPolicies = properties["FieldIndexPolicies"];
7847
+ if (fieldIndexPolicies && fieldIndexPolicies.length > 0) {
7848
+ if (fieldIndexPolicies.length > 1) this.logger.debug(`Log group ${logicalId} declares ${fieldIndexPolicies.length} FieldIndexPolicies; AWS only supports one log-group-level field index policy. Applying the first.`);
7849
+ const first = fieldIndexPolicies[0];
7850
+ const policyDocument = typeof first === "string" ? first : JSON.stringify(first);
7851
+ await this.logsClient.send(new PutIndexPolicyCommand({
7852
+ logGroupIdentifier: logGroupName,
7853
+ policyDocument
7854
+ }));
7855
+ }
7856
+ if (properties["BearerTokenAuthenticationEnabled"] !== void 0) await this.logsClient.send(new PutBearerTokenAuthenticationCommand({
7832
7857
  logGroupIdentifier: logGroupName,
7833
- policyDocument
7858
+ bearerTokenAuthenticationEnabled: properties["BearerTokenAuthenticationEnabled"]
7834
7859
  }));
7860
+ } catch (innerError) {
7861
+ if (createdNewLogGroup) try {
7862
+ await this.logsClient.send(new DeleteLogGroupCommand({ logGroupName }));
7863
+ this.logger.debug(`Cleaned up partially-created log group ${logicalId} (${logGroupName}) after wiring failure`);
7864
+ } catch (cleanupError) {
7865
+ this.logger.warn(`Failed to clean up partially-created log group ${logicalId} (${logGroupName}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws logs delete-log-group --log-group-name '${logGroupName}'`);
7866
+ }
7867
+ throw innerError;
7835
7868
  }
7836
- if (properties["BearerTokenAuthenticationEnabled"] !== void 0) await this.logsClient.send(new PutBearerTokenAuthenticationCommand({
7837
- logGroupIdentifier: logGroupName,
7838
- bearerTokenAuthenticationEnabled: properties["BearerTokenAuthenticationEnabled"]
7839
- }));
7840
7869
  this.logger.debug(`Successfully created log group ${logicalId}: ${logGroupName}`);
7841
7870
  return {
7842
7871
  physicalId: logGroupName,
7843
7872
  attributes: { Arn: await this.buildArn(logGroupName) }
7844
7873
  };
7845
7874
  } catch (error) {
7846
- if (error instanceof ResourceAlreadyExistsException) {
7847
- this.logger.debug(`Log group ${logGroupName} already exists, using existing`);
7848
- return {
7849
- physicalId: logGroupName,
7850
- attributes: { Arn: await this.buildArn(logGroupName) }
7851
- };
7852
- }
7853
7875
  const cause = error instanceof Error ? error : void 0;
7854
7876
  throw new ProvisioningError(`Failed to create log group ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, logGroupName, cause);
7855
7877
  }
@@ -8756,16 +8778,26 @@ var SSMParameterProvider = class {
8756
8778
  if (properties["Policies"]) putParams.Policies = properties["Policies"];
8757
8779
  if (properties["DataType"]) putParams.DataType = properties["DataType"];
8758
8780
  await this.ssmClient.send(new PutParameterCommand(putParams));
8759
- if (properties["Tags"]) {
8760
- const ssmTags = properties["Tags"].map((t) => ({
8761
- Key: t.Key,
8762
- Value: t.Value
8763
- }));
8764
- await this.ssmClient.send(new AddTagsToResourceCommand({
8765
- ResourceType: "Parameter",
8766
- ResourceId: name,
8767
- Tags: ssmTags
8768
- }));
8781
+ try {
8782
+ if (properties["Tags"]) {
8783
+ const ssmTags = properties["Tags"].map((t) => ({
8784
+ Key: t.Key,
8785
+ Value: t.Value
8786
+ }));
8787
+ await this.ssmClient.send(new AddTagsToResourceCommand({
8788
+ ResourceType: "Parameter",
8789
+ ResourceId: name,
8790
+ Tags: ssmTags
8791
+ }));
8792
+ }
8793
+ } catch (innerError) {
8794
+ try {
8795
+ await this.ssmClient.send(new DeleteParameterCommand({ Name: name }));
8796
+ this.logger.debug(`Cleaned up partially-created SSM parameter ${logicalId} (${name}) after wiring failure`);
8797
+ } catch (cleanupError) {
8798
+ this.logger.warn(`Failed to clean up partially-created SSM parameter ${logicalId} (${name}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws ssm delete-parameter --name '${name}'`);
8799
+ }
8800
+ throw innerError;
8769
8801
  }
8770
8802
  this.logger.debug(`Successfully created SSM parameter ${logicalId}: ${name}`);
8771
8803
  return {
@@ -12357,6 +12389,21 @@ var ApiGatewayProvider = class ApiGatewayProvider {
12357
12389
  * `Invalid mapping expression specified: ... [No method response exists
12358
12390
  * for method.]` — the canonical trigger is a CORS preflight OPTIONS
12359
12391
  * method emitted by `RestApi({ defaultCorsPreflightOptions: ... })`.
12392
+ *
12393
+ * Partial-failure cleanup: if any AWS call AFTER `PutMethodCommand`
12394
+ * fails, the method has already been created on AWS but cdkd state
12395
+ * does NOT record it (the throw happens before the success return).
12396
+ * A subsequent redeploy would then attempt CREATE again and AWS would
12397
+ * reject with `Method already exists for this resource`. To prevent
12398
+ * this orphan, the post-`PutMethod` block is wrapped in an inner
12399
+ * try/catch that issues a best-effort `DeleteMethodCommand` before
12400
+ * re-throwing the original error. Cleanup failures are logged at warn
12401
+ * (the underlying create failure is what matters; we don't mask it by
12402
+ * promoting a cleanup error). The class of bug — partial AWS-side
12403
+ * commit on `createMethod` failure — was first seen via the
12404
+ * `PutIntegrationResponse`-before-`PutMethodResponse` ordering bug
12405
+ * fixed in PR #373; this cleanup makes any future shape of
12406
+ * post-`PutMethod` failure self-healing on the next redeploy.
12360
12407
  */
12361
12408
  async createMethod(logicalId, resourceType, properties) {
12362
12409
  this.logger.debug(`Creating API Gateway Method ${logicalId}`);
@@ -12386,54 +12433,68 @@ var ApiGatewayProvider = class ApiGatewayProvider {
12386
12433
  requestValidatorId,
12387
12434
  authorizationScopes
12388
12435
  }));
12389
- const integration = properties["Integration"];
12390
- if (integration) await this.apiGatewayClient.send(new PutIntegrationCommand({
12391
- restApiId,
12392
- resourceId,
12393
- httpMethod,
12394
- type: integration["Type"],
12395
- integrationHttpMethod: integration["IntegrationHttpMethod"],
12396
- uri: integration["Uri"],
12397
- connectionType: integration["ConnectionType"],
12398
- connectionId: integration["ConnectionId"],
12399
- credentials: integration["Credentials"],
12400
- requestParameters: integration["RequestParameters"],
12401
- requestTemplates: integration["RequestTemplates"],
12402
- passthroughBehavior: integration["PassthroughBehavior"],
12403
- contentHandling: integration["ContentHandling"],
12404
- timeoutInMillis: integration["TimeoutInMillis"],
12405
- cacheNamespace: integration["CacheNamespace"],
12406
- cacheKeyParameters: integration["CacheKeyParameters"],
12407
- tlsConfig: integration["TlsConfig"] ? { insecureSkipVerification: integration["TlsConfig"]["InsecureSkipVerification"] } : void 0,
12408
- responseTransferMode: integration["ResponseTransferMode"]
12409
- }));
12410
- const methodResponses = properties["MethodResponses"];
12411
- if (methodResponses) for (const resp of methodResponses) {
12412
- const statusCode = String(resp["StatusCode"]);
12413
- await this.apiGatewayClient.send(new PutMethodResponseCommand({
12436
+ try {
12437
+ const integration = properties["Integration"];
12438
+ if (integration) await this.apiGatewayClient.send(new PutIntegrationCommand({
12414
12439
  restApiId,
12415
12440
  resourceId,
12416
12441
  httpMethod,
12417
- statusCode,
12418
- responseModels: resp["ResponseModels"],
12419
- responseParameters: resp["ResponseParameters"]
12442
+ type: integration["Type"],
12443
+ integrationHttpMethod: integration["IntegrationHttpMethod"],
12444
+ uri: integration["Uri"],
12445
+ connectionType: integration["ConnectionType"],
12446
+ connectionId: integration["ConnectionId"],
12447
+ credentials: integration["Credentials"],
12448
+ requestParameters: integration["RequestParameters"],
12449
+ requestTemplates: integration["RequestTemplates"],
12450
+ passthroughBehavior: integration["PassthroughBehavior"],
12451
+ contentHandling: integration["ContentHandling"],
12452
+ timeoutInMillis: integration["TimeoutInMillis"],
12453
+ cacheNamespace: integration["CacheNamespace"],
12454
+ cacheKeyParameters: integration["CacheKeyParameters"],
12455
+ tlsConfig: integration["TlsConfig"] ? { insecureSkipVerification: integration["TlsConfig"]["InsecureSkipVerification"] } : void 0,
12456
+ responseTransferMode: integration["ResponseTransferMode"]
12420
12457
  }));
12421
- }
12422
- if (integration) {
12423
- const integrationResponses = integration["IntegrationResponses"];
12424
- if (integrationResponses) for (const ir of integrationResponses) {
12425
- const statusCode = String(ir["StatusCode"]);
12426
- await this.apiGatewayClient.send(new PutIntegrationResponseCommand({
12458
+ const methodResponses = properties["MethodResponses"];
12459
+ if (methodResponses) for (const resp of methodResponses) {
12460
+ const statusCode = String(resp["StatusCode"]);
12461
+ await this.apiGatewayClient.send(new PutMethodResponseCommand({
12427
12462
  restApiId,
12428
12463
  resourceId,
12429
12464
  httpMethod,
12430
12465
  statusCode,
12431
- selectionPattern: ir["SelectionPattern"],
12432
- responseParameters: ir["ResponseParameters"],
12433
- responseTemplates: ir["ResponseTemplates"],
12434
- contentHandling: ir["ContentHandling"]
12466
+ responseModels: resp["ResponseModels"],
12467
+ responseParameters: resp["ResponseParameters"]
12468
+ }));
12469
+ }
12470
+ if (integration) {
12471
+ const integrationResponses = integration["IntegrationResponses"];
12472
+ if (integrationResponses) for (const ir of integrationResponses) {
12473
+ const statusCode = String(ir["StatusCode"]);
12474
+ await this.apiGatewayClient.send(new PutIntegrationResponseCommand({
12475
+ restApiId,
12476
+ resourceId,
12477
+ httpMethod,
12478
+ statusCode,
12479
+ selectionPattern: ir["SelectionPattern"],
12480
+ responseParameters: ir["ResponseParameters"],
12481
+ responseTemplates: ir["ResponseTemplates"],
12482
+ contentHandling: ir["ContentHandling"]
12483
+ }));
12484
+ }
12485
+ }
12486
+ } catch (innerError) {
12487
+ try {
12488
+ await this.apiGatewayClient.send(new DeleteMethodCommand({
12489
+ restApiId,
12490
+ resourceId,
12491
+ httpMethod
12435
12492
  }));
12493
+ this.logger.debug(`Cleaned up partially-created API Gateway Method ${logicalId} (${restApiId}/${resourceId}/${httpMethod}) after wiring failure`);
12494
+ } catch (cleanupError) {
12495
+ this.logger.warn(`Failed to clean up partially-created API Gateway Method ${logicalId} (${restApiId}/${resourceId}/${httpMethod}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws apigateway delete-method --rest-api-id ${restApiId} --resource-id ${resourceId} --http-method ${httpMethod}`);
12436
12496
  }
12497
+ throw innerError;
12437
12498
  }
12438
12499
  const physicalId = `${restApiId}|${resourceId}|${httpMethod}`;
12439
12500
  this.logger.debug(`Successfully created API Gateway Method ${logicalId}: ${physicalId}`);
@@ -42709,7 +42770,7 @@ function reorderArgs(argv) {
42709
42770
  */
42710
42771
  async function main() {
42711
42772
  const program = new Command();
42712
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.102.1");
42773
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.102.3");
42713
42774
  program.addCommand(createBootstrapCommand());
42714
42775
  program.addCommand(createSynthCommand());
42715
42776
  program.addCommand(createListCommand());