@go-to-k/cdkd 0.29.0 → 0.30.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/dist/cli.js CHANGED
@@ -1304,14 +1304,14 @@ async function resolveStateBucketWithDefaultAndSource(cliBucket, region) {
1304
1304
  const logger = getLogger();
1305
1305
  logger.debug("No state bucket specified, resolving default from account...");
1306
1306
  const { GetCallerIdentityCommand: GetCallerIdentityCommand11 } = await import("@aws-sdk/client-sts");
1307
- const { S3Client: S3Client11 } = await import("@aws-sdk/client-s3");
1307
+ const { S3Client: S3Client12 } = await import("@aws-sdk/client-s3");
1308
1308
  const { getAwsClients: getAwsClients2 } = await Promise.resolve().then(() => (init_aws_clients(), aws_clients_exports));
1309
1309
  const awsClients = getAwsClients2();
1310
1310
  const identity = await awsClients.sts.send(new GetCallerIdentityCommand11({}));
1311
1311
  const accountId = identity.Account;
1312
1312
  const newName = getDefaultStateBucketName(accountId);
1313
1313
  const legacyName = getLegacyStateBucketName(accountId, region);
1314
- const probe = new S3Client11({ region: "us-east-1" });
1314
+ const probe = new S3Client12({ region: "us-east-1" });
1315
1315
  try {
1316
1316
  const newExists = await bucketExists(probe, newName);
1317
1317
  const legacyExists = await bucketExists(probe, legacyName);
@@ -35330,6 +35330,7 @@ import {
35330
35330
  waitUntilStackUpdateComplete,
35331
35331
  waitUntilStackDeleteComplete
35332
35332
  } from "@aws-sdk/client-cloudformation";
35333
+ import { S3Client as S3Client11, PutObjectCommand as PutObjectCommand5, DeleteObjectCommand as DeleteObjectCommand4 } from "@aws-sdk/client-s3";
35333
35334
  var STABLE_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
35334
35335
  "CREATE_COMPLETE",
35335
35336
  "UPDATE_COMPLETE",
@@ -35338,9 +35339,11 @@ var STABLE_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
35338
35339
  "IMPORT_ROLLBACK_COMPLETE"
35339
35340
  ]);
35340
35341
  var TEMPLATE_BODY_LIMIT = 51200;
35342
+ var TEMPLATE_URL_LIMIT = 1048576;
35343
+ var MIGRATE_TMP_PREFIX = "cdkd-migrate-tmp";
35341
35344
  async function retireCloudFormationStack(options) {
35342
35345
  const logger = getLogger();
35343
- const { cfnStackName, cfnClient, yes } = options;
35346
+ const { cfnStackName, cfnClient, yes, stateBucket, s3ClientOpts } = options;
35344
35347
  logger.info(`[1/4] Inspecting CloudFormation stack '${cfnStackName}'...`);
35345
35348
  const desc = await cfnClient.send(new DescribeStacksCommand({ StackName: cfnStackName }));
35346
35349
  const stack = desc.Stacks?.[0];
@@ -35370,39 +35373,69 @@ async function retireCloudFormationStack(options) {
35370
35373
  return { outcome: "cancelled" };
35371
35374
  }
35372
35375
  }
35373
- let updateRan = false;
35374
35376
  if (!modified) {
35375
35377
  logger.info(`[2/4] Template already has Retain on every resource \u2014 skipping UpdateStack.`);
35376
35378
  } else {
35377
35379
  logger.info(`[2/4] Injected DeletionPolicy=Retain and UpdateReplacePolicy=Retain.`);
35378
- if (newBody.length > TEMPLATE_BODY_LIMIT) {
35380
+ if (newBody.length > TEMPLATE_URL_LIMIT) {
35379
35381
  throw new Error(
35380
- `Modified template is ${newBody.length} bytes, exceeds the inline UpdateStack TemplateBody limit (${TEMPLATE_BODY_LIMIT}). cdkd state has already been written; retire the stack manually with: (1) edit the template to add DeletionPolicy: Retain and UpdateReplacePolicy: Retain to every resource, (2) UpdateStack with the modified template via S3 TemplateURL, (3) DeleteStack. Inline TemplateURL fallback is a planned follow-up.`
35382
+ `Modified template is ${newBody.length} bytes, exceeds the CloudFormation UpdateStack TemplateURL limit (${TEMPLATE_URL_LIMIT}). cdkd state has already been written; retire the stack manually with (1) shrink the template, then (2) UpdateStack with Retain policies, (3) DeleteStack \u2014 or split the stack and retry.`
35381
35383
  );
35382
35384
  }
35383
- logger.info(`[3/4] Updating CloudFormation stack with Retain policies...`);
35384
- try {
35385
- await cfnClient.send(
35386
- new UpdateStackCommand({
35387
- StackName: cfnStackName,
35388
- TemplateBody: newBody,
35389
- Capabilities: capabilities
35390
- })
35385
+ let updateInput;
35386
+ let s3Cleanup;
35387
+ if (newBody.length <= TEMPLATE_BODY_LIMIT) {
35388
+ updateInput = { TemplateBody: newBody };
35389
+ } else {
35390
+ logger.info(
35391
+ ` Template is ${newBody.length} bytes (over ${TEMPLATE_BODY_LIMIT} inline limit) \u2014 uploading to state bucket '${stateBucket}'.`
35391
35392
  );
35392
- updateRan = true;
35393
- } catch (err) {
35394
- const msg = err instanceof Error ? err.message : String(err);
35395
- if (/No updates are to be performed/i.test(msg)) {
35396
- logger.info(` CloudFormation reports no updates needed \u2014 proceeding to delete.`);
35397
- } else {
35398
- throw err;
35399
- }
35393
+ const uploaded = await uploadTemplateForUpdateStack({
35394
+ bucket: stateBucket,
35395
+ body: newBody,
35396
+ cfnStackName,
35397
+ ...s3ClientOpts && { s3ClientOpts }
35398
+ });
35399
+ updateInput = { TemplateURL: uploaded.url };
35400
+ s3Cleanup = uploaded.cleanup;
35400
35401
  }
35401
- if (updateRan) {
35402
- await waitUntilStackUpdateComplete(
35403
- { client: cfnClient, maxWaitTime: 1800 },
35404
- { StackName: cfnStackName }
35405
- );
35402
+ try {
35403
+ logger.info(`[3/4] Updating CloudFormation stack with Retain policies...`);
35404
+ let updateRan = false;
35405
+ try {
35406
+ await cfnClient.send(
35407
+ new UpdateStackCommand({
35408
+ StackName: cfnStackName,
35409
+ ...updateInput,
35410
+ Capabilities: capabilities
35411
+ })
35412
+ );
35413
+ updateRan = true;
35414
+ } catch (err) {
35415
+ const msg = err instanceof Error ? err.message : String(err);
35416
+ if (/No updates are to be performed/i.test(msg)) {
35417
+ logger.info(` CloudFormation reports no updates needed \u2014 proceeding to delete.`);
35418
+ } else {
35419
+ throw err;
35420
+ }
35421
+ }
35422
+ if (updateRan) {
35423
+ await waitUntilStackUpdateComplete(
35424
+ { client: cfnClient, maxWaitTime: 1800 },
35425
+ { StackName: cfnStackName }
35426
+ );
35427
+ }
35428
+ } finally {
35429
+ if (s3Cleanup) {
35430
+ try {
35431
+ await s3Cleanup();
35432
+ } catch (cleanupErr) {
35433
+ const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
35434
+ logger.warn(
35435
+ `Failed to delete temporary template upload from '${stateBucket}'. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`
35436
+ );
35437
+ }
35438
+ }
35406
35439
  }
35407
35440
  }
35408
35441
  logger.info(`[4/4] Deleting CloudFormation stack '${cfnStackName}' (resources retained)...`);
@@ -35416,6 +35449,41 @@ async function retireCloudFormationStack(options) {
35416
35449
  );
35417
35450
  return { outcome: modified ? "retired" : "no-template-change" };
35418
35451
  }
35452
+ async function uploadTemplateForUpdateStack(args) {
35453
+ const { bucket, body, cfnStackName, s3ClientOpts } = args;
35454
+ const region = await resolveBucketRegion(bucket, {
35455
+ ...s3ClientOpts?.profile && { profile: s3ClientOpts.profile },
35456
+ ...s3ClientOpts?.credentials && { credentials: s3ClientOpts.credentials }
35457
+ });
35458
+ const s3 = new S3Client11({
35459
+ region,
35460
+ ...s3ClientOpts?.profile && { profile: s3ClientOpts.profile },
35461
+ ...s3ClientOpts?.credentials && { credentials: s3ClientOpts.credentials }
35462
+ });
35463
+ const key = `${MIGRATE_TMP_PREFIX}/${cfnStackName}/${Date.now()}.json`;
35464
+ try {
35465
+ await s3.send(
35466
+ new PutObjectCommand5({
35467
+ Bucket: bucket,
35468
+ Key: key,
35469
+ Body: body,
35470
+ ContentType: "application/json"
35471
+ })
35472
+ );
35473
+ } catch (err) {
35474
+ s3.destroy();
35475
+ throw err;
35476
+ }
35477
+ const url = `https://${bucket}.s3.${region}.amazonaws.com/${key}`;
35478
+ const cleanup = async () => {
35479
+ try {
35480
+ await s3.send(new DeleteObjectCommand4({ Bucket: bucket, Key: key }));
35481
+ } finally {
35482
+ s3.destroy();
35483
+ }
35484
+ };
35485
+ return { url, cleanup };
35486
+ }
35419
35487
  function injectRetainPolicies(templateBody, cfnStackName) {
35420
35488
  let parsed;
35421
35489
  try {
@@ -35695,7 +35763,13 @@ async function importCommand(stackArg, options) {
35695
35763
  await retireCloudFormationStack({
35696
35764
  cfnStackName: migrationCfnStackName,
35697
35765
  cfnClient: awsClients.cloudFormation,
35698
- yes: options.yes
35766
+ yes: options.yes,
35767
+ // Reuse cdkd's state bucket as transient storage for the
35768
+ // Retain-injected template when it exceeds the 51,200-byte
35769
+ // inline UpdateStack limit. Forward `--profile` so the
35770
+ // upload identity matches the one that just wrote cdkd state.
35771
+ stateBucket,
35772
+ ...options.profile && { s3ClientOpts: { profile: options.profile } }
35699
35773
  });
35700
35774
  }
35701
35775
  } finally {
@@ -35988,7 +36062,7 @@ function reorderArgs(argv) {
35988
36062
  }
35989
36063
  async function main() {
35990
36064
  const program = new Command13();
35991
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.29.0");
36065
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.30.0");
35992
36066
  program.addCommand(createBootstrapCommand());
35993
36067
  program.addCommand(createSynthCommand());
35994
36068
  program.addCommand(createListCommand());