@go-to-k/cdkd 0.231.12 → 0.233.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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { A as withErrorHandling, B as generateResourceName, C as StackHasActiveImportsError, F as getLiveRenderer, H as withSkipPrefix, I as PATTERN_B_NAME_OPTIONS, L as PATTERN_B_NAME_PROPERTIES, M as getLogger, P as runStackBuffered, R as PATTERN_B_RESOURCE_TYPES, S as ResourceUpdateNotSupportedError, U as withStackName, V as generateResourceNameWithFallback, _ as MissingCdkCliError, a as normalizeAwsTagsToCfn, b as ProvisioningError, f as LocalInvokeBuildError$1, i as matchesCdkPath, k as normalizeAwsError, l as CdkdError, m as LocalStartServiceError, n as isTerminationProtectionPropagationError, o as resolveExplicitPhysicalId, p as LocalMigrateError, r as CDK_PATH_TAG, s as assertRegionMatch, t as disableInstanceApiTermination, v as NestedStackChildDirectDestroyError, w as StackTerminationProtectionError, x as ResourceTimeoutError, y as PartialFailureError } from "./ec2-termination-protection-uvVnteGl.js";
3
3
  import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-pjPwZz1r.js";
4
- import { $ as resolveSkipPrefix, A as DiffCalculator, B as buildDockerImage, C as findActionableSilentDrops, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as AssetManifestLoader, H as getDockerCmd, I as shouldRetainResource, J as synthesisStatusMessage, K as getDockerImageBySourceHash, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as resolveCaptureObservedState, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as runDockerForeground, V as formatDockerLoginError, W as runDockerStreaming, X as getLegacyStateBucketName, Y as getDefaultStateBucketName, Z as resolveApp, _ as green, a as withRetry, at as MIGRATE_TMP_PREFIX, b as IAMRoleProvider, c as computeImplicitDeleteEdges, ct as AssemblyReader, d as isStatefulRecreateTargetSync, et as resolveStateBucketWithDefault, f as renderStatefulReason, g as gray, h as cyan, i as withResourceDeadline, it as CFN_TEMPLATE_URL_LIMIT, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, m as bold, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as warnDeprecatedNoPrefixCliFlag, o as isRetryableTransientError, ot as findLargeInlineResources, p as formatResourceLine, q as Synthesizer, r as DeployEngine, rt as CFN_TEMPLATE_BODY_LIMIT, s as IMPLICIT_DELETE_DEPENDENCIES, st as uploadCfnTemplate, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as resolveStateBucketWithDefaultAndSource, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveBucketRegion, v as red, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, y as yellow, z as WorkGraph } from "./deploy-engine-BkPZar1R.js";
4
+ import { $ as AssetManifestLoader, A as DiffCalculator, B as buildAssetRedirectMap, C as findActionableSilentDrops, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as BOOTSTRAP_MARKER_PREFIX, H as loadPublishableAssetManifest, I as shouldRetainResource, J as buildDockerImage, K as ensureAssetStorage, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as runDockerStreaming, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as rewriteTemplateAssetReferences, V as createAssetRedirectResolver, W as AssetModeResolver, X as getDockerCmd, Y as formatDockerLoginError, Z as runDockerForeground, _ as green, _t as AssemblyReader, a as withRetry, at as resolveApp, b as IAMRoleProvider, c as computeImplicitDeleteEdges, ct as resolveStateBucketWithDefault, d as isStatefulRecreateTargetSync, dt as warnDeprecatedNoPrefixCliFlag, et as getDockerImageBySourceHash, f as renderStatefulReason, ft as CFN_TEMPLATE_BODY_LIMIT, g as gray, gt as uploadCfnTemplate, h as cyan, ht as findLargeInlineResources, i as withResourceDeadline, it as getLegacyStateBucketName, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, lt as resolveStateBucketWithDefaultAndSource, m as bold, mt as MIGRATE_TMP_PREFIX, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as synthesisStatusMessage, o as isRetryableTransientError, ot as resolveCaptureObservedState, p as formatResourceLine, pt as CFN_TEMPLATE_URL_LIMIT, q as parseBootstrapMarker, r as DeployEngine, rt as getDefaultStateBucketName, s as IMPLICIT_DELETE_DEPENDENCIES, st as resolveSkipPrefix, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as Synthesizer, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveUseCdkBootstrapAssets, v as red, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, y as yellow, yt as resolveBucketRegion, z as WorkGraph } from "./deploy-engine-Dj9mXUQa.js";
5
5
  import { t as ASGProvider } from "./asg-provider-Dd9Xze-T.js";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  import { createHash, randomBytes, randomUUID } from "node:crypto";
@@ -492,6 +492,14 @@ const recreateViaSdkProviderOption = new Option("--recreate-via-sdk-provider <lo
492
492
  */
493
493
  const forceStatefulRecreationOption = new Option("--force-stateful-recreation", "Bypass the stateful-resource guard for --recreate-via-cc-api targets. Required when ANY named target is a stateful type (RDS / DynamoDB / EFS / S3 with data / Logs with retention / Cognito / Secrets / SSM / Glue / ECR / CloudFront / Kinesis / OpenSearch). Destroy + recreate loses ALL data in the resource — no automatic data migration. Triple-opt-in for CI use: --recreate-via-cc-api <id> --force-stateful-recreation --yes.").default(false);
494
494
  const replaceOption = new Option("--replace", "Replace (DELETE + CREATE) a resource whose in-place update is rejected because an immutable property changed and AWS exposes no update API for it (e.g. Lambda LayerVersion content, EFS AccessPoint, ECS TaskDefinition, ApiGatewayV2 immutable fields). Without this flag such a change fails the deploy. Stateful types (RDS / DynamoDB / EFS / S3 with data / Logs with retention / etc.) ALSO require --force-stateful-recreation since the replacement is a data-losing DELETE + CREATE.").default(false);
495
+ /**
496
+ * Issue #1002 PR 2 — per-invocation legacy pin for asset destinations
497
+ * (design §4.2). Shared by `deploy`, `diff`, `import`, and `publish-assets`
498
+ * (every command the redirection / rewrite affects) so the CLI surface stays
499
+ * consistent; the per-app pin is `cdk.json context.cdkd.useCdkBootstrapAssets`
500
+ * (see `resolveUseCdkBootstrapAssets` in config-loader.ts).
501
+ */
502
+ const useCdkBootstrapAssetsOption = new Option("--use-cdk-bootstrap-assets", "Publish assets to the CDK bootstrap bucket/repo named by the asset manifest verbatim, skipping the cdkd asset-storage redirection even when this region is opted in via 'cdkd bootstrap'. For apps deployed via both CloudFormation and cdkd during a migration window. Per-app pin: cdk.json context.cdkd.useCdkBootstrapAssets. Also suppresses the legacy-mode 'cdk gc' notice.").default(false);
495
503
  const deployOptions = [
496
504
  new Option("--concurrency <number>", "Maximum concurrent resource operations").default(10).argParser((value) => parseInt(value, 10)),
497
505
  new Option("--stack-concurrency <number>", "Maximum concurrent stack deployments").default(4).argParser((value) => parseInt(value, 10)),
@@ -511,6 +519,7 @@ const deployOptions = [
511
519
  recreateViaSdkProviderOption,
512
520
  forceStatefulRecreationOption,
513
521
  replaceOption,
522
+ useCdkBootstrapAssetsOption,
514
523
  ...resourceTimeoutOptions
515
524
  ];
516
525
  /**
@@ -603,7 +612,11 @@ const destroyOptions = [
603
612
  /**
604
613
  * Bootstrap command implementation
605
614
  *
606
- * Creates S3 bucket for state management
615
+ * Creates the S3 bucket for state management and (unless --no-assets) the
616
+ * cdkd-owned asset storage for the region: asset bucket + container-asset
617
+ * ECR repo + the per-region bootstrap marker that flips deploys in this
618
+ * region from legacy (CDK bootstrap destinations) to cdkd-assets mode
619
+ * (issue #1002, design at docs/design/1002-cdkd-asset-storage.md).
607
620
  */
608
621
  async function bootstrapCommand(options) {
609
622
  const logger = getLogger();
@@ -646,54 +659,96 @@ async function bootstrapCommand(options) {
646
659
  operation: "HeadBucket"
647
660
  });
648
661
  }
649
- if (bucketExists) {
650
- if (!options.force) {
662
+ let skipStateBucketConfig = false;
663
+ if (bucketExists) if (!options.force) {
664
+ if (!options.assets) {
651
665
  logger.warn(`Bucket ${bucketName} already exists. Use --force to reconfigure (this will not delete existing state)`);
652
666
  return;
653
667
  }
654
- logger.info("--force specified, continuing with existing bucket");
655
- } else {
668
+ logger.info(`State bucket ${bucketName} already exists — skipping reconfiguration (use --force to reconfigure)`);
669
+ skipStateBucketConfig = true;
670
+ } else logger.info("--force specified, continuing with existing bucket");
671
+ else {
656
672
  logger.info(`Creating S3 bucket: ${bucketName} in region ${region}`);
657
673
  const createBucketParams = { Bucket: bucketName };
658
674
  if (region !== "us-east-1") createBucketParams.CreateBucketConfiguration = { LocationConstraint: region };
659
675
  await s3Client.send(new CreateBucketCommand(createBucketParams));
660
676
  logger.info(`✓ Created S3 bucket: ${bucketName}`);
661
677
  }
662
- logger.debug("Enabling bucket versioning...");
663
- await s3Client.send(new PutBucketVersioningCommand({
664
- Bucket: bucketName,
665
- VersioningConfiguration: { Status: "Enabled" }
666
- }));
667
- logger.info("✓ Enabled bucket versioning");
668
- logger.debug("Enabling bucket encryption...");
669
- await s3Client.send(new PutBucketEncryptionCommand({
670
- Bucket: bucketName,
671
- ServerSideEncryptionConfiguration: { Rules: [{
672
- ApplyServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" },
673
- BucketKeyEnabled: true
674
- }] }
675
- }));
676
- logger.info("✓ Enabled bucket encryption (AES-256)");
677
- logger.debug("Setting bucket policy...");
678
- const bucketPolicy = {
679
- Version: "2012-10-17",
680
- Statement: [{
681
- Sid: "DenyExternalAccess",
682
- Effect: "Deny",
683
- Principal: "*",
684
- Action: "s3:*",
685
- Resource: [`arn:aws:s3:::${bucketName}`, `arn:aws:s3:::${bucketName}/*`],
686
- Condition: { StringNotEquals: { "aws:PrincipalAccount": accountId } }
687
- }]
688
- };
689
- await s3Client.send(new PutBucketPolicyCommand({
690
- Bucket: bucketName,
691
- Policy: JSON.stringify(bucketPolicy)
692
- }));
693
- logger.info("✓ Set bucket policy (deny external access)");
678
+ if (!skipStateBucketConfig) {
679
+ logger.debug("Enabling bucket versioning...");
680
+ await s3Client.send(new PutBucketVersioningCommand({
681
+ Bucket: bucketName,
682
+ VersioningConfiguration: { Status: "Enabled" }
683
+ }));
684
+ logger.info(" Enabled bucket versioning");
685
+ logger.debug("Enabling bucket encryption...");
686
+ await s3Client.send(new PutBucketEncryptionCommand({
687
+ Bucket: bucketName,
688
+ ServerSideEncryptionConfiguration: { Rules: [{
689
+ ApplyServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" },
690
+ BucketKeyEnabled: true
691
+ }] }
692
+ }));
693
+ logger.info(" Enabled bucket encryption (AES-256)");
694
+ logger.debug("Setting bucket policy...");
695
+ const bucketPolicy = {
696
+ Version: "2012-10-17",
697
+ Statement: [{
698
+ Sid: "DenyExternalAccess",
699
+ Effect: "Deny",
700
+ Principal: "*",
701
+ Action: "s3:*",
702
+ Resource: [`arn:aws:s3:::${bucketName}`, `arn:aws:s3:::${bucketName}/*`],
703
+ Condition: { StringNotEquals: { "aws:PrincipalAccount": accountId } }
704
+ }]
705
+ };
706
+ await s3Client.send(new PutBucketPolicyCommand({
707
+ Bucket: bucketName,
708
+ Policy: JSON.stringify(bucketPolicy)
709
+ }));
710
+ logger.info("✓ Set bucket policy (deny external access)");
711
+ }
712
+ let assetStorage;
713
+ if (options.assets) {
714
+ logger.info("\nSetting up cdkd asset storage...");
715
+ const ecrClient = new ECRClient({
716
+ region,
717
+ ...options.profile && { profile: options.profile }
718
+ });
719
+ const markerS3Client = new S3Client({
720
+ region,
721
+ ...options.profile && { profile: options.profile }
722
+ });
723
+ const stateBackend = new S3StateBackend(markerS3Client, {
724
+ bucket: bucketName,
725
+ prefix: "cdkd"
726
+ }, {
727
+ region,
728
+ ...options.profile && { profile: options.profile }
729
+ });
730
+ try {
731
+ assetStorage = await ensureAssetStorage({
732
+ s3Client,
733
+ ecrClient,
734
+ stateBackend,
735
+ accountId,
736
+ region,
737
+ force: options.force
738
+ });
739
+ } finally {
740
+ ecrClient.destroy();
741
+ markerS3Client.destroy();
742
+ }
743
+ } else logger.info("\n--no-assets specified — skipping cdkd asset storage. Deploys in this region keep publishing assets to the CDK bootstrap bucket/repo.");
694
744
  logger.info("\n✓ Bootstrap completed successfully");
695
745
  logger.info(`\nState bucket: ${bucketName}`);
746
+ if (assetStorage) {
747
+ logger.info(`Asset bucket: ${assetStorage.assetBucket}`);
748
+ logger.info(`Container-asset repository: ${assetStorage.containerRepo}`);
749
+ }
696
750
  logger.info(`Region: ${region}`);
751
+ if (assetStorage) logger.info(`\ncdkd asset storage is now ON for region ${region}: deploys in this region publish assets to the cdkd-owned bucket/repo (out of 'cdk gc' reach) instead of the CDK bootstrap storage. The first deploy of each existing stack with assets will show a one-time UPDATE re-pointing asset references — content is identical, no replacement.`);
697
752
  logger.info("\nYou can now use cdkd deploy with:");
698
753
  logger.info(` --state-bucket ${bucketName}`);
699
754
  logger.info(` --region ${region}`);
@@ -705,7 +760,7 @@ async function bootstrapCommand(options) {
705
760
  * Create bootstrap command
706
761
  */
707
762
  function createBootstrapCommand() {
708
- const cmd = new Command("bootstrap").description("Bootstrap cdkd by creating required S3 bucket for state management").option("--state-bucket <bucket>", "Name of S3 bucket to create for state storage (default: cdkd-state-{accountId})").option("--force", "Force reconfiguration of existing bucket", false).addOption(new Option("--region <region>", "AWS region in which to create the state bucket (defaults to AWS_REGION env or us-east-1)")).action(withErrorHandling(bootstrapCommand));
763
+ const cmd = new Command("bootstrap").description("Bootstrap cdkd by creating the S3 state bucket plus cdkd-owned asset storage (asset bucket + container-asset ECR repo) for the region").option("--state-bucket <bucket>", "Name of S3 bucket to create for state storage (default: cdkd-state-{accountId})").option("--force", "Force reconfiguration of existing bucket", false).option("--no-assets", "Skip cdkd asset storage (asset bucket / ECR repo / marker) — deploys keep publishing assets to the CDK bootstrap bucket/repo").addOption(new Option("--region <region>", "AWS region in which to create the state bucket (defaults to AWS_REGION env or us-east-1)")).action(withErrorHandling(bootstrapCommand));
709
764
  commonOptions.forEach((opt) => cmd.addOption(opt));
710
765
  return cmd;
711
766
  }
@@ -1445,7 +1500,7 @@ const FLUSH_INTERVAL_MS = 2e3;
1445
1500
  const FLUSH_EVENT_THRESHOLD = 50;
1446
1501
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
1447
1502
  function getCdkdVersion() {
1448
- return "0.231.12";
1503
+ return "0.233.0";
1449
1504
  }
1450
1505
  /**
1451
1506
  * Generate a time-sortable unique run id, e.g.
@@ -36769,7 +36824,7 @@ var NestedStackProvider = class {
36769
36824
  this.requireDeployContext(ctx, "create");
36770
36825
  const childTemplatePath = ctx.nestedTemplates[logicalId];
36771
36826
  if (!childTemplatePath) throw new Error(`Nested template file not found for AWS::CloudFormation::Stack '${logicalId}' under parent '${ctx.parentStackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default).`);
36772
- const childTemplate = this.readChildTemplate(childTemplatePath);
36827
+ const childTemplate = this.readChildTemplate(childTemplatePath, ctx.assetRedirect);
36773
36828
  const childStackName = this.deriveChildStackName(ctx.parentStackName, logicalId);
36774
36829
  const childRegion = ctx.parentRegion;
36775
36830
  const childParameters = this.extractParameters(properties);
@@ -36788,7 +36843,7 @@ var NestedStackProvider = class {
36788
36843
  this.requireDeployContext(ctx, "update");
36789
36844
  const childTemplatePath = ctx.nestedTemplates[logicalId];
36790
36845
  if (!childTemplatePath) throw new Error(`Nested template file not found for AWS::CloudFormation::Stack '${logicalId}' on update.`);
36791
- const childTemplate = this.readChildTemplate(childTemplatePath);
36846
+ const childTemplate = this.readChildTemplate(childTemplatePath, ctx.assetRedirect);
36792
36847
  const childStackName = this.deriveChildStackName(ctx.parentStackName, logicalId);
36793
36848
  const childRegion = ctx.parentRegion;
36794
36849
  const childParameters = this.extractParameters(properties);
@@ -36883,18 +36938,24 @@ var NestedStackProvider = class {
36883
36938
  else throw new Error(`NestedStackProvider: child Parameter '${k}' resolved to a non-scalar value (type=${v === null ? "null" : typeof v}). Parameters must be scalars (string / number / boolean) by the time they reach the provider — an unresolved intrinsic here means IntrinsicFunctionResolver upstream did not handle the value, which is a bug. Surface the unresolved input rather than silently coercing to '[object Object]'.`);
36884
36939
  return result;
36885
36940
  }
36886
- readChildTemplate(templatePath) {
36941
+ readChildTemplate(templatePath, assetRedirect) {
36887
36942
  let raw;
36888
36943
  try {
36889
36944
  raw = fs.readFileSync(templatePath, "utf-8");
36890
36945
  } catch (err) {
36891
36946
  throw new Error(`Failed to read nested template at ${templatePath}: ${err instanceof Error ? err.message : String(err)}`);
36892
36947
  }
36948
+ let template;
36893
36949
  try {
36894
- return JSON.parse(raw);
36950
+ template = JSON.parse(raw);
36895
36951
  } catch (err) {
36896
36952
  throw new Error(`Failed to parse nested template at ${templatePath}: ${err instanceof Error ? err.message : String(err)}`);
36897
36953
  }
36954
+ if (assetRedirect) {
36955
+ const n = rewriteTemplateAssetReferences(template, assetRedirect);
36956
+ if (n > 0) this.logger.debug(`Rewrote ${n} asset reference(s) to cdkd asset storage in nested template ${templatePath}`);
36957
+ }
36958
+ return template;
36898
36959
  }
36899
36960
  indexGrandchildTemplates(childTemplate, childTemplatePath) {
36900
36961
  const dir = path.dirname(childTemplatePath);
@@ -37644,6 +37705,11 @@ async function deployCommand(stacks, options) {
37644
37705
  const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
37645
37706
  stsClient.destroy();
37646
37707
  const assetPublisher = new AssetPublisher();
37708
+ const useCdkBootstrapAssets = resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets);
37709
+ const assetModeResolver = new AssetModeResolver(preflightStateBackend, accountId, {
37710
+ ...options.profile && { profile: options.profile },
37711
+ ...useCdkBootstrapAssets && { useCdkBootstrapAssets: true }
37712
+ });
37647
37713
  const stateConfig = {
37648
37714
  bucket: stateBucket,
37649
37715
  prefix: options.statePrefix
@@ -37657,20 +37723,36 @@ async function deployCommand(stacks, options) {
37657
37723
  };
37658
37724
  const workGraph = new WorkGraph();
37659
37725
  const stackMap = new Map(targetStacks.map((s) => [s.stackName, s]));
37726
+ const stackRedirects = /* @__PURE__ */ new Map();
37660
37727
  for (const stack of targetStacks) {
37661
37728
  const stackNodeId = `stack:${stack.stackName}`;
37662
37729
  const stackDeps = /* @__PURE__ */ new Set();
37663
- if (!options.skipAssets && stack.assetManifestPath) try {
37730
+ if (stack.assetManifestPath) {
37664
37731
  const assetRegion = stack.region || baseRegion;
37665
- const nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
37666
- accountId,
37667
- region: assetRegion,
37668
- ...options.profile && { profile: options.profile },
37669
- nodePrefix: `${stack.stackName}:`
37670
- });
37671
- for (const id of nodeIds) stackDeps.add(id);
37672
- } catch (error) {
37673
- if (error.code !== "ENOENT") throw error;
37732
+ const manifest = loadPublishableAssetManifest(stack.assetManifestPath);
37733
+ if (manifest) {
37734
+ const assetMode = await assetModeResolver.resolve(assetRegion);
37735
+ logger.debug(`Asset mode for region ${assetRegion}: ${assetMode.mode} (stack ${stack.stackName})`);
37736
+ let redirect;
37737
+ if (assetMode.mode === "cdkd-assets") {
37738
+ redirect = buildAssetRedirectMap(manifest, assetMode.marker, accountId, assetRegion);
37739
+ if (redirect.entries.length > 0) {
37740
+ stackRedirects.set(stack.stackName, redirect);
37741
+ const rewritten = rewriteTemplateAssetReferences(stack.template, redirect);
37742
+ logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage (${assetMode.marker.assetBucket} / ${assetMode.marker.containerRepo}) in template of stack ${stack.stackName}`);
37743
+ } else redirect = void 0;
37744
+ }
37745
+ if (!options.skipAssets) {
37746
+ const nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
37747
+ accountId,
37748
+ region: assetRegion,
37749
+ ...options.profile && { profile: options.profile },
37750
+ nodePrefix: `${stack.stackName}:`,
37751
+ ...redirect && { redirect }
37752
+ });
37753
+ for (const id of nodeIds) stackDeps.add(id);
37754
+ }
37755
+ }
37674
37756
  }
37675
37757
  for (const depName of effectiveStackDeps(stack.stackName, stack.dependencyNames)) if (stackMap.has(depName)) stackDeps.add(`stack:${depName}`);
37676
37758
  workGraph.addNode({
@@ -37769,10 +37851,12 @@ async function deployCommand(stacks, options) {
37769
37851
  })) return;
37770
37852
  }
37771
37853
  }
37854
+ const assetRedirect = stackRedirects.get(stackInfo.stackName);
37772
37855
  const deployEngineOptions = {
37773
37856
  concurrency: options.concurrency,
37774
37857
  dryRun: options.dryRun,
37775
37858
  noRollback: !options.rollback,
37859
+ ...assetRedirect && { assetRedirect },
37776
37860
  ...eventRecorder && { eventRecorder },
37777
37861
  ...recreateViaCcApiTargets && recreateViaCcApiTargets.size > 0 && { recreateViaCcApiTargets },
37778
37862
  ...recreateViaSdkProviderTargets && recreateViaSdkProviderTargets.size > 0 && { recreateViaSdkProviderTargets },
@@ -37798,7 +37882,8 @@ async function deployCommand(stacks, options) {
37798
37882
  nestedTemplates: stackInfo.nestedTemplates ?? {},
37799
37883
  dagBuilder,
37800
37884
  diffCalculator,
37801
- options: deployEngineOptions
37885
+ options: deployEngineOptions,
37886
+ ...assetRedirect && { assetRedirect }
37802
37887
  }, () => stackDeployEngine.deploy(stackInfo.stackName, stackInfo.template));
37803
37888
  logger.info(`\n${bold("Deployment Summary:")}`);
37804
37889
  logger.info(` Stack: ${bold(cyan(deployResult.stackName))}`);
@@ -38836,7 +38921,7 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
38836
38921
  * fail at deploy time.
38837
38922
  */
38838
38923
  async function buildDiffTree(args) {
38839
- const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters } = args;
38924
+ const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters, assetRedirect } = args;
38840
38925
  const state = await loadStateOrEmpty(stackName, region, stateBackend);
38841
38926
  const node = {
38842
38927
  stackName,
@@ -38855,6 +38940,7 @@ async function buildDiffTree(args) {
38855
38940
  if (!childTemplatePath) throw new Error(`Nested template file not found for ${NESTED_STACK_RESOURCE_TYPE} '${logicalId}' under stack '${stackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default), then re-run synth.`);
38856
38941
  const childStackName = `${stackName}~${logicalId}`;
38857
38942
  const childTemplate = readNestedTemplate(childTemplatePath);
38943
+ if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
38858
38944
  const grandchildTemplates = indexNestedChildTemplates(childTemplate, childTemplatePath);
38859
38945
  const childParameters = await resolveChildStackParameters(resource, template, state, region, stackName, stateBackend, parameters);
38860
38946
  node.children.push(await buildDiffTree({
@@ -38866,7 +38952,8 @@ async function buildDiffTree(args) {
38866
38952
  recursive: true,
38867
38953
  stateBackend,
38868
38954
  diffCalculator,
38869
- parameters: childParameters
38955
+ parameters: childParameters,
38956
+ ...assetRedirect && { assetRedirect }
38870
38957
  }));
38871
38958
  }
38872
38959
  for (const [logicalId, resource] of Object.entries(state.resources)) {
@@ -39176,10 +39263,22 @@ async function diffCommand(stacks, options) {
39176
39263
  });
39177
39264
  const diffCalculator = new DiffCalculator();
39178
39265
  const recursive = options.recursive ?? false;
39266
+ const resolveAssetRedirect = createAssetRedirectResolver({
39267
+ stateBackend,
39268
+ stsRegion: region,
39269
+ ...options.profile && { profile: options.profile },
39270
+ useCdkBootstrapAssets: resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets),
39271
+ suppressLegacyNotice: true
39272
+ });
39179
39273
  const trees = [];
39180
39274
  for (const stackInfo of targetStacks) {
39181
39275
  logger.info(`\nCalculating diff for stack: ${stackInfo.stackName}`);
39182
39276
  const stackRegion = stackInfo.region || region;
39277
+ const assetRedirect = await resolveAssetRedirect(stackInfo.assetManifestPath, stackRegion);
39278
+ if (assetRedirect) {
39279
+ const rewritten = rewriteTemplateAssetReferences(stackInfo.template, assetRedirect);
39280
+ logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage in template of stack ${stackInfo.stackName}`);
39281
+ }
39183
39282
  trees.push(await buildDiffTree({
39184
39283
  stackName: stackInfo.stackName,
39185
39284
  displayName: stackInfo.stackName,
@@ -39188,7 +39287,8 @@ async function diffCommand(stacks, options) {
39188
39287
  nestedTemplates: stackInfo.nestedTemplates ?? {},
39189
39288
  recursive,
39190
39289
  stateBackend,
39191
- diffCalculator
39290
+ diffCalculator,
39291
+ ...assetRedirect && { assetRedirect }
39192
39292
  }));
39193
39293
  }
39194
39294
  if (options.json) process.stdout.write(`${JSON.stringify(trees.map(diffTreeToJson), null, 2)}\n`);
@@ -39208,7 +39308,7 @@ async function diffCommand(stacks, options) {
39208
39308
  * Create diff command
39209
39309
  */
39210
39310
  function createDiffCommand() {
39211
- const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).action(withErrorHandling(diffCommand));
39311
+ const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(diffCommand));
39212
39312
  [
39213
39313
  ...commonOptions,
39214
39314
  ...appOptions,
@@ -41501,36 +41601,45 @@ async function publishAssetsCommand(stacks, options) {
41501
41601
  const stsClient = new STSClient({ region: baseRegion });
41502
41602
  const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
41503
41603
  stsClient.destroy();
41604
+ const useCdkBootstrapAssets = resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets);
41605
+ let markerAccess;
41606
+ const getModeResolver = async () => {
41607
+ if (markerAccess !== void 0) return markerAccess?.resolver ?? null;
41608
+ let bucket;
41609
+ try {
41610
+ bucket = await resolveStateBucketWithDefault(options.stateBucket, baseRegion);
41611
+ } catch {
41612
+ logger.info("No cdkd state bucket found — publishing to the asset-manifest destinations (CDK bootstrap storage) verbatim. Run 'cdkd bootstrap' to opt into cdkd-owned asset storage that 'cdk gc' never touches.");
41613
+ markerAccess = null;
41614
+ return null;
41615
+ }
41616
+ const markerClients = new AwsClients({
41617
+ region: baseRegion,
41618
+ ...options.profile && { profile: options.profile }
41619
+ });
41620
+ const resolver = new AssetModeResolver(new S3StateBackend(markerClients.s3, {
41621
+ bucket,
41622
+ prefix: options.statePrefix
41623
+ }, {
41624
+ region: baseRegion,
41625
+ ...options.profile && { profile: options.profile }
41626
+ }), accountId, { ...options.profile && { profile: options.profile } });
41627
+ markerAccess = {
41628
+ resolver,
41629
+ dispose: () => markerClients.destroy()
41630
+ };
41631
+ return resolver;
41632
+ };
41504
41633
  const assetPublisher = new AssetPublisher();
41505
41634
  const results = [];
41506
- for (const stack of targetStacks) {
41507
- const startedAt = Date.now();
41508
- let assetCount = 0;
41509
- let error;
41510
- try {
41511
- if (!stack.assetManifestPath) {
41512
- logger.debug(`Stack ${stack.stackName} has no asset manifest; nothing to publish`);
41513
- results.push({
41514
- stackName: stack.stackName,
41515
- displayName: stack.displayName,
41516
- assetCount: 0,
41517
- durationMs: Date.now() - startedAt
41518
- });
41519
- continue;
41520
- }
41521
- logger.info(`\nPublishing assets for stack: ${describeStack(stack)}`);
41522
- const workGraph = new WorkGraph();
41523
- let nodeIds = [];
41635
+ try {
41636
+ for (const stack of targetStacks) {
41637
+ const startedAt = Date.now();
41638
+ let assetCount = 0;
41639
+ let error;
41524
41640
  try {
41525
- nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
41526
- accountId,
41527
- region: stack.region || baseRegion,
41528
- ...options.profile && { profile: options.profile },
41529
- nodePrefix: `${stack.stackName}:`
41530
- });
41531
- } catch (err) {
41532
- if (err.code === "ENOENT") {
41533
- logger.debug(`Asset manifest not found for ${stack.stackName} (${stack.assetManifestPath}); skipping`);
41641
+ if (!stack.assetManifestPath) {
41642
+ logger.debug(`Stack ${stack.stackName} has no asset manifest; nothing to publish`);
41534
41643
  results.push({
41535
41644
  stackName: stack.stackName,
41536
41645
  displayName: stack.displayName,
@@ -41539,36 +41648,76 @@ async function publishAssetsCommand(stacks, options) {
41539
41648
  });
41540
41649
  continue;
41541
41650
  }
41542
- throw err;
41543
- }
41544
- assetCount = nodeIds.filter((id) => id.startsWith("asset-publish:")).length;
41545
- if (assetCount === 0) {
41546
- logger.info(" (no assets to publish)");
41547
- results.push({
41548
- stackName: stack.stackName,
41549
- displayName: stack.displayName,
41550
- assetCount: 0,
41551
- durationMs: Date.now() - startedAt
41552
- });
41553
- continue;
41651
+ logger.info(`\nPublishing assets for stack: ${describeStack(stack)}`);
41652
+ let redirect;
41653
+ if (!useCdkBootstrapAssets) {
41654
+ const manifest = loadPublishableAssetManifest(stack.assetManifestPath);
41655
+ if (manifest) {
41656
+ const modeResolver = await getModeResolver();
41657
+ if (modeResolver) {
41658
+ const stackRegion = stack.region || baseRegion;
41659
+ const assetMode = await modeResolver.resolve(stackRegion);
41660
+ if (assetMode.mode === "cdkd-assets") {
41661
+ const map = buildAssetRedirectMap(manifest, assetMode.marker, accountId, stackRegion);
41662
+ if (map.entries.length > 0) redirect = map;
41663
+ }
41664
+ }
41665
+ }
41666
+ }
41667
+ const workGraph = new WorkGraph();
41668
+ let nodeIds = [];
41669
+ try {
41670
+ nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
41671
+ accountId,
41672
+ region: stack.region || baseRegion,
41673
+ ...options.profile && { profile: options.profile },
41674
+ nodePrefix: `${stack.stackName}:`,
41675
+ ...redirect && { redirect }
41676
+ });
41677
+ } catch (err) {
41678
+ if (err.code === "ENOENT") {
41679
+ logger.debug(`Asset manifest not found for ${stack.stackName} (${stack.assetManifestPath}); skipping`);
41680
+ results.push({
41681
+ stackName: stack.stackName,
41682
+ displayName: stack.displayName,
41683
+ assetCount: 0,
41684
+ durationMs: Date.now() - startedAt
41685
+ });
41686
+ continue;
41687
+ }
41688
+ throw err;
41689
+ }
41690
+ assetCount = nodeIds.filter((id) => id.startsWith("asset-publish:")).length;
41691
+ if (assetCount === 0) {
41692
+ logger.info(" (no assets to publish)");
41693
+ results.push({
41694
+ stackName: stack.stackName,
41695
+ displayName: stack.displayName,
41696
+ assetCount: 0,
41697
+ durationMs: Date.now() - startedAt
41698
+ });
41699
+ continue;
41700
+ }
41701
+ await workGraph.execute({
41702
+ "asset-build": options.imageBuildConcurrency,
41703
+ "asset-publish": options.assetPublishConcurrency,
41704
+ stack: 0
41705
+ }, (node) => assetPublisher.executeNode(node));
41706
+ logger.info(` ✓ Published ${assetCount} asset(s) in ${((Date.now() - startedAt) / 1e3).toFixed(2)}s`);
41707
+ } catch (err) {
41708
+ error = err instanceof Error ? err : new Error(String(err));
41709
+ logger.error(` ✗ ${stack.stackName}: ${error.message}`);
41554
41710
  }
41555
- await workGraph.execute({
41556
- "asset-build": options.imageBuildConcurrency,
41557
- "asset-publish": options.assetPublishConcurrency,
41558
- stack: 0
41559
- }, (node) => assetPublisher.executeNode(node));
41560
- logger.info(` ✓ Published ${assetCount} asset(s) in ${((Date.now() - startedAt) / 1e3).toFixed(2)}s`);
41561
- } catch (err) {
41562
- error = err instanceof Error ? err : new Error(String(err));
41563
- logger.error(` ✗ ${stack.stackName}: ${error.message}`);
41564
- }
41565
- results.push({
41566
- stackName: stack.stackName,
41567
- displayName: stack.displayName,
41568
- assetCount,
41569
- durationMs: Date.now() - startedAt,
41570
- ...error && { error }
41571
- });
41711
+ results.push({
41712
+ stackName: stack.stackName,
41713
+ displayName: stack.displayName,
41714
+ assetCount,
41715
+ durationMs: Date.now() - startedAt,
41716
+ ...error && { error }
41717
+ });
41718
+ }
41719
+ } finally {
41720
+ markerAccess?.dispose();
41572
41721
  }
41573
41722
  const failed = results.filter((r) => r.error);
41574
41723
  const totalAssets = results.reduce((sum, r) => sum + r.assetCount, 0);
@@ -41591,11 +41740,12 @@ async function publishAssetsCommand(stacks, options) {
41591
41740
  * `cdkd deploy --no-asset-publish` (which doesn't exist).
41592
41741
  */
41593
41742
  function createPublishAssetsCommand() {
41594
- const cmd = new Command("publish-assets").description("Synthesize the CDK app and publish assets to S3/ECR for the selected stack(s) without deploying").argument("[stacks...]", "Stack name(s) to publish assets for. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards.").option("--all", "Publish assets for all stacks", false).addOption(new Option("--asset-publish-concurrency <number>", "Maximum concurrent asset publish operations").default(8).argParser((value) => parseInt(value, 10))).addOption(new Option("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))).action(withErrorHandling(publishAssetsCommand));
41743
+ const cmd = new Command("publish-assets").description("Synthesize the CDK app and publish assets to S3/ECR for the selected stack(s) without deploying").argument("[stacks...]", "Stack name(s) to publish assets for. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards.").option("--all", "Publish assets for all stacks", false).addOption(new Option("--asset-publish-concurrency <number>", "Maximum concurrent asset publish operations").default(8).argParser((value) => parseInt(value, 10))).addOption(new Option("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))).addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(publishAssetsCommand));
41595
41744
  [
41596
41745
  ...commonOptions,
41597
41746
  ...appOptions,
41598
- ...contextOptions
41747
+ ...contextOptions,
41748
+ ...stateOptions
41599
41749
  ].forEach((opt) => cmd.addOption(opt));
41600
41750
  cmd.addOption(new Option("--stack <name>", "Stack name to publish (alternative to positional)"));
41601
41751
  cmd.addOption(deprecatedRegionOption);
@@ -45188,6 +45338,45 @@ async function readSchemaVersion(awsClients, bucket, keys) {
45188
45338
  }
45189
45339
  }
45190
45340
  /**
45341
+ * List every region's bootstrap marker under `cdkd-bootstrap/` in the state
45342
+ * bucket. Malformed markers are skipped with a warning — `state info` is a
45343
+ * cosmetic command and should not crash on an unexpected payload (deploy
45344
+ * hard-errors on the same marker instead).
45345
+ */
45346
+ async function listAssetStorageMarkers(awsClients, bucket) {
45347
+ const logger = getLogger();
45348
+ const entries = [];
45349
+ let continuationToken;
45350
+ const keys = [];
45351
+ do {
45352
+ const resp = await awsClients.s3.send(new ListObjectsV2Command({
45353
+ Bucket: bucket,
45354
+ Prefix: BOOTSTRAP_MARKER_PREFIX,
45355
+ ...continuationToken && { ContinuationToken: continuationToken }
45356
+ }));
45357
+ for (const obj of resp.Contents ?? []) if (typeof obj.Key === "string" && obj.Key.startsWith("cdkd-bootstrap/") && obj.Key.endsWith(".json")) keys.push(obj.Key);
45358
+ continuationToken = resp.NextContinuationToken;
45359
+ } while (continuationToken);
45360
+ for (const key of keys) {
45361
+ const region = key.slice(BOOTSTRAP_MARKER_PREFIX.length, -5);
45362
+ try {
45363
+ const marker = parseBootstrapMarker(await (await awsClients.s3.send(new GetObjectCommand({
45364
+ Bucket: bucket,
45365
+ Key: key
45366
+ }))).Body?.transformToString() ?? "", key);
45367
+ entries.push({
45368
+ region,
45369
+ assetBucket: marker.assetBucket,
45370
+ containerRepo: marker.containerRepo,
45371
+ createdAt: marker.createdAt
45372
+ });
45373
+ } catch (error) {
45374
+ logger.warn(`Skipping malformed/unreadable bootstrap marker '${key}': ${error.message}`);
45375
+ }
45376
+ }
45377
+ return entries.sort((a, b) => a.region.localeCompare(b.region));
45378
+ }
45379
+ /**
45191
45380
  * `cdkd state info` command implementation.
45192
45381
  *
45193
45382
  * Prints the state-bucket information that used to appear as a banner on
@@ -45229,6 +45418,7 @@ async function stateInfoCommand(options) {
45229
45418
  const detectedRegion = await detectBucketRegion(awsClients, bucket);
45230
45419
  const stateFileKeys = await listStateFileKeys(awsClients, bucket, prefix);
45231
45420
  const schemaVersion = await readSchemaVersion(awsClients, bucket, stateFileKeys);
45421
+ const assetStorage = await listAssetStorageMarkers(awsClients, bucket);
45232
45422
  if (options.json) {
45233
45423
  const json = {
45234
45424
  bucket,
@@ -45236,7 +45426,8 @@ async function stateInfoCommand(options) {
45236
45426
  regionSource: detectedRegion ? "auto-detected" : "unknown",
45237
45427
  bucketSource: resolved.source,
45238
45428
  schemaVersion,
45239
- stackCount: stateFileKeys.length
45429
+ stackCount: stateFileKeys.length,
45430
+ assetStorage
45240
45431
  };
45241
45432
  process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
45242
45433
  return;
@@ -45248,6 +45439,11 @@ async function stateInfoCommand(options) {
45248
45439
  lines.push(`Source: ${formatBucketSource(resolved.source)}`);
45249
45440
  lines.push(`Schema version: ${schemaVersion}`);
45250
45441
  lines.push(`Stacks: ${stateFileKeys.length}`);
45442
+ if (assetStorage.length === 0) lines.push("Asset storage: legacy (CDK bootstrap) — run cdkd bootstrap to opt in");
45443
+ else {
45444
+ lines.push(`Asset storage: cdkd-assets mode in ${assetStorage.length} region(s)`);
45445
+ for (const entry of assetStorage) lines.push(` ${entry.region}: ${entry.assetBucket} / ${entry.containerRepo}`);
45446
+ }
45251
45447
  process.stdout.write(`${lines.join("\n")}\n`);
45252
45448
  } finally {
45253
45449
  awsClients.destroy();
@@ -45573,6 +45769,17 @@ async function importCommand(stackArg, options) {
45573
45769
  else throw new Error(`Multiple stacks found: ${result.stacks.map((s) => s.stackName).join(", ")}. Specify the stack name as a positional argument.`);
45574
45770
  const targetRegion = stackInfo.region || region;
45575
45771
  logger.info(`Target stack: ${stackInfo.stackName} (${targetRegion})`);
45772
+ const assetRedirect = await createAssetRedirectResolver({
45773
+ stateBackend,
45774
+ stsRegion: region,
45775
+ ...options.profile && { profile: options.profile },
45776
+ useCdkBootstrapAssets: resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets),
45777
+ suppressLegacyNotice: true
45778
+ })(stackInfo.assetManifestPath, targetRegion);
45779
+ if (assetRedirect) {
45780
+ const rewritten = rewriteTemplateAssetReferences(stackInfo.template, assetRedirect);
45781
+ logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage in template of stack ${stackInfo.stackName}`);
45782
+ }
45576
45783
  const overrides = parseResourceOverrides(options.resource, options.resourceMapping, options.resourceMappingInline);
45577
45784
  if (overrides.size > 0) logger.debug(`User-supplied physical IDs: ${[...overrides.keys()].join(", ")}`);
45578
45785
  const migrationCfnStackName = options.migrateFromCloudformation ? typeof options.migrateFromCloudformation === "string" && options.migrateFromCloudformation.length > 0 ? options.migrateFromCloudformation : stackInfo.stackName : void 0;
@@ -45709,7 +45916,8 @@ async function importCommand(stackArg, options) {
45709
45916
  templateParser,
45710
45917
  lockOwner: owner,
45711
45918
  accountId: accountIdForNestedSynth,
45712
- logger
45919
+ logger,
45920
+ assetRedirect
45713
45921
  });
45714
45922
  if (migrationCfnStackName) {
45715
45923
  const orphaned = resources.length - importedRows.length;
@@ -46124,7 +46332,7 @@ async function confirmPrompt(prompt) {
46124
46332
  * default behavior, now opt-in.
46125
46333
  */
46126
46334
  function createImportCommand() {
46127
- const cmd = new Command("import").description("Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest.").argument("[stack]", "Stack to import. Optional when the synthesized app contains exactly one stack.").option("--resource <id=physical>", "Explicit physical-id override for one logical ID. Repeatable. When at least one --resource is given, only listed resources are imported (CDK CLI parity). Pass --auto to also tag-import everything else.", collectMultiple, []).option("--resource-mapping <file>", "Path to a JSON file of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping` compatible). Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping-inline.").option("--resource-mapping-inline <json>", "Inline JSON object of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping-inline` compatible). Same shape as --resource-mapping but supplied as a string — useful for non-TTY CI scripts that do not want a separate file. Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping.").option("--record-resource-mapping <file>", "After cdkd resolves every logical ID (via --resource / --resource-mapping / tag-based auto-lookup), write the resulting {logicalId: physicalId} map to <file> as JSON. Useful in auto / hybrid mode for capturing the tag-resolved mapping and feeding it back as --resource-mapping in non-interactive CI re-runs. Written before the confirmation prompt (so the user can review the file before saying \"yes\") and even when the user says \"no\". Mirrors `cdk import --record-resource-mapping`.").option("--auto", "Hybrid mode: when explicit overrides are supplied, ALSO tag-import every other resource in the template. Without this flag, --resource / --resource-mapping behave as a whitelist (CDK CLI parity).", false).option("--dry-run", "Show planned imports without writing state", false).option("--force", "Confirm a destructive write to existing state. Required for auto / whole-stack import when state already exists (rebuilds the entire resource map). Also required in selective mode if a listed override would overwrite a resource already in state. Not needed for a pure selective merge (adding new resources without touching unlisted entries).", false).option("--migrate-from-cloudformation [cfn-stack-name]", "After cdkd state is written, retire the named CloudFormation stack (deletes the CFn stack record; AWS resources are NOT deleted): inject DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource via UpdateStack, then DeleteStack. cdkd takes over management. Pass without a value to use the cdkd stack name as the CFn stack name (the typical case for a CDK app that was previously deployed via `cdk deploy`); pass an explicit value when the CFn stack name differs.").action(withErrorHandling(importCommand));
46335
+ const cmd = new Command("import").description("Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest.").argument("[stack]", "Stack to import. Optional when the synthesized app contains exactly one stack.").option("--resource <id=physical>", "Explicit physical-id override for one logical ID. Repeatable. When at least one --resource is given, only listed resources are imported (CDK CLI parity). Pass --auto to also tag-import everything else.", collectMultiple, []).option("--resource-mapping <file>", "Path to a JSON file of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping` compatible). Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping-inline.").option("--resource-mapping-inline <json>", "Inline JSON object of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping-inline` compatible). Same shape as --resource-mapping but supplied as a string — useful for non-TTY CI scripts that do not want a separate file. Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping.").option("--record-resource-mapping <file>", "After cdkd resolves every logical ID (via --resource / --resource-mapping / tag-based auto-lookup), write the resulting {logicalId: physicalId} map to <file> as JSON. Useful in auto / hybrid mode for capturing the tag-resolved mapping and feeding it back as --resource-mapping in non-interactive CI re-runs. Written before the confirmation prompt (so the user can review the file before saying \"yes\") and even when the user says \"no\". Mirrors `cdk import --record-resource-mapping`.").option("--auto", "Hybrid mode: when explicit overrides are supplied, ALSO tag-import every other resource in the template. Without this flag, --resource / --resource-mapping behave as a whitelist (CDK CLI parity).", false).option("--dry-run", "Show planned imports without writing state", false).option("--force", "Confirm a destructive write to existing state. Required for auto / whole-stack import when state already exists (rebuilds the entire resource map). Also required in selective mode if a listed override would overwrite a resource already in state. Not needed for a pure selective merge (adding new resources without touching unlisted entries).", false).option("--migrate-from-cloudformation [cfn-stack-name]", "After cdkd state is written, retire the named CloudFormation stack (deletes the CFn stack record; AWS resources are NOT deleted): inject DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource via UpdateStack, then DeleteStack. cdkd takes over management. Pass without a value to use the cdkd stack name as the CFn stack name (the typical case for a CDK app that was previously deployed via `cdk deploy`); pass an explicit value when the CFn stack name differs.").addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(importCommand));
46128
46336
  [
46129
46337
  ...commonOptions,
46130
46338
  ...appOptions,
@@ -46262,7 +46470,7 @@ function validateNestedStackShape(template, tree, parentStackName, nestedTemplat
46262
46470
  * is verified by unit test.
46263
46471
  */
46264
46472
  async function importNestedStackChildrenRecursive(args) {
46265
- const { parentStackName, parentRegion, parentNestedTemplates, parentTree, stateBackend, lockManager, providerRegistry, templateParser, lockOwner, accountId, logger } = args;
46473
+ const { parentStackName, parentRegion, parentNestedTemplates, parentTree, stateBackend, lockManager, providerRegistry, templateParser, lockOwner, accountId, logger, assetRedirect } = args;
46266
46474
  for (const [childLogicalId, childTreeNode] of parentTree.nested) {
46267
46475
  const childStackName = `${parentStackName}~${childLogicalId}`;
46268
46476
  const childRegion = parentRegion;
@@ -46270,6 +46478,7 @@ async function importNestedStackChildrenRecursive(args) {
46270
46478
  if (!childTemplatePath) throw new Error(`cdkd import --migrate-from-cloudformation: missing nested-template path for '${childLogicalId}' under parent '${parentStackName}' — validateNestedStackShape should have rejected this; please file a bug.`);
46271
46479
  logger.info(`Adopting nested stack '${childLogicalId}' as cdkd stack '${childStackName}' (${childRegion})...`);
46272
46480
  const childTemplate = readNestedChildTemplate(childTemplatePath, childLogicalId);
46481
+ if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
46273
46482
  await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import");
46274
46483
  try {
46275
46484
  const childResources = collectImportableResources(childTemplate);
@@ -46321,7 +46530,8 @@ async function importNestedStackChildrenRecursive(args) {
46321
46530
  templateParser,
46322
46531
  lockOwner,
46323
46532
  accountId,
46324
- logger
46533
+ logger,
46534
+ assetRedirect
46325
46535
  });
46326
46536
  } finally {
46327
46537
  await lockManager.releaseLock(childStackName, childRegion).catch((err) => {
@@ -55779,7 +55989,7 @@ function reorderArgs(argv) {
55779
55989
  async function main() {
55780
55990
  installPipeCloseHandler();
55781
55991
  const program = new Command();
55782
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.231.12");
55992
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.233.0");
55783
55993
  program.addCommand(createBootstrapCommand());
55784
55994
  program.addCommand(createSynthCommand());
55785
55995
  program.addCommand(createListCommand());