@go-to-k/cdkd 0.25.0 → 0.27.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
@@ -528,29 +528,82 @@ function parseDuration(value) {
528
528
  const multiplier = unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
529
529
  return Math.round(num * multiplier);
530
530
  }
531
+ var RESOURCE_TYPE_REGEX = /^[A-Z][A-Za-z0-9]+::[A-Z][A-Za-z0-9]+::[A-Z][A-Za-z0-9]+$/;
532
+ function parseResourceTimeoutToken(flagName) {
533
+ return (raw, previous) => {
534
+ const acc = previous ?? { perTypeMs: {} };
535
+ if (!acc.perTypeMs)
536
+ acc.perTypeMs = {};
537
+ const eqIndex = raw.indexOf("=");
538
+ if (eqIndex === -1) {
539
+ acc.globalMs = parseDuration(raw);
540
+ return acc;
541
+ }
542
+ const typePart = raw.substring(0, eqIndex).trim();
543
+ const durationPart = raw.substring(eqIndex + 1).trim();
544
+ if (!RESOURCE_TYPE_REGEX.test(typePart)) {
545
+ throw new Error(
546
+ `Invalid ${flagName} value "${raw}": left-hand side must be a CloudFormation resource type like AWS::Service::Resource (got "${typePart}")`
547
+ );
548
+ }
549
+ if (durationPart.length === 0) {
550
+ throw new Error(
551
+ `Invalid ${flagName} value "${raw}": missing duration after '=' (e.g. ${typePart}=1h)`
552
+ );
553
+ }
554
+ let ms;
555
+ try {
556
+ ms = parseDuration(durationPart);
557
+ } catch (err) {
558
+ const inner = err instanceof Error ? err.message : String(err);
559
+ throw new Error(`Invalid ${flagName} value "${raw}": ${inner}`);
560
+ }
561
+ acc.perTypeMs[typePart] = ms;
562
+ return acc;
563
+ };
564
+ }
531
565
  var resourceTimeoutOptions = [
532
- // Default values are stored as parsed milliseconds (NOT the source
533
- // string) because commander's `argParser` only runs on user-supplied
534
- // values, never on defaults without this every command handler
535
- // would see `'5m'` (string) when the user did not pass the flag and
536
- // `300_000` (number) when they did. The second `defaultValueDescription`
537
- // argument keeps `--help` showing the human-readable form.
566
+ // Default is `undefined` (NOT a pre-seeded ResourceTimeoutOption) the
567
+ // command handler resolves missing globalMs to DEFAULT_RESOURCE_*_MS
568
+ // at the call site. Pre-seeding here would force every accumulator
569
+ // call to carry a snapshot, and would also surprise unit tests that
570
+ // expect `opts.resourceTimeout` to be `undefined` when the flag is not
571
+ // passed.
538
572
  new Option(
539
- "--resource-warn-after <duration>",
540
- "Warn when a single resource operation exceeds this wall-clock duration (e.g. 5m, 90s, 1h)"
541
- ).default(parseDuration("5m"), "5m").argParser(parseDuration),
573
+ "--resource-warn-after <duration_or_type=duration>",
574
+ "Warn when a single resource operation exceeds this wall-clock duration. Repeatable: pass a bare duration (e.g. 5m) to set the global default, or TYPE=DURATION (e.g. AWS::CloudFront::Distribution=10m) for a per-type override."
575
+ ).default(void 0, "5m").argParser(parseResourceTimeoutToken("--resource-warn-after")),
542
576
  new Option(
543
- "--resource-timeout <duration>",
544
- "Abort a single resource operation that exceeds this wall-clock duration. Custom-Resource-heavy stacks may need to raise this above the default 30m (the Custom Resource provider's polling cap is 1h)."
545
- ).default(parseDuration("30m"), "30m").argParser(parseDuration)
577
+ "--resource-timeout <duration_or_type=duration>",
578
+ "Abort a single resource operation that exceeds this wall-clock duration. Repeatable: pass a bare duration (e.g. 30m) to set the global default, or TYPE=DURATION (e.g. AWS::CloudFront::Distribution=1h) for a per-type override. Custom-Resource-heavy stacks may need to raise this above the default 30m (the Custom Resource provider's polling cap is 1h)."
579
+ ).default(void 0, "30m").argParser(parseResourceTimeoutToken("--resource-timeout"))
546
580
  ];
547
581
  function validateResourceTimeouts(opts) {
548
582
  const warn = opts.resourceWarnAfter;
549
583
  const timeout = opts.resourceTimeout;
550
- if (typeof warn === "number" && typeof timeout === "number" && warn >= timeout) {
551
- throw new Error(
552
- `--resource-warn-after (${warn}ms) must be less than --resource-timeout (${timeout}ms)`
553
- );
584
+ const globalWarn = warn?.globalMs;
585
+ const globalTimeout = timeout?.globalMs;
586
+ if (typeof globalWarn === "number" && typeof globalTimeout === "number") {
587
+ if (globalWarn >= globalTimeout) {
588
+ throw new Error(
589
+ `--resource-warn-after (${globalWarn}ms) must be less than --resource-timeout (${globalTimeout}ms)`
590
+ );
591
+ }
592
+ }
593
+ const warnPerType = warn?.perTypeMs ?? {};
594
+ const timeoutPerType = timeout?.perTypeMs ?? {};
595
+ const types = /* @__PURE__ */ new Set([...Object.keys(warnPerType), ...Object.keys(timeoutPerType)]);
596
+ for (const t of types) {
597
+ const effectiveWarn = warnPerType[t] ?? globalWarn;
598
+ const effectiveTimeout = timeoutPerType[t] ?? globalTimeout;
599
+ if (typeof effectiveWarn !== "number" || typeof effectiveTimeout !== "number") {
600
+ continue;
601
+ }
602
+ if (effectiveWarn >= effectiveTimeout) {
603
+ throw new Error(
604
+ `--resource-warn-after for ${t} (${effectiveWarn}ms) must be less than --resource-timeout for ${t} (${effectiveTimeout}ms)`
605
+ );
606
+ }
554
607
  }
555
608
  }
556
609
  var deployOptions = [
@@ -8585,6 +8638,33 @@ var IAMRoleProvider = class {
8585
8638
  this.logger.debug(`Added/updated ${tagsToAdd.length} tags on role ${roleName}`);
8586
8639
  }
8587
8640
  }
8641
+ /**
8642
+ * Resolve a single `Fn::GetAtt` attribute for an existing IAM role.
8643
+ *
8644
+ * CloudFormation's `AWS::IAM::Role` exposes `Arn` and `RoleId`; both are
8645
+ * available from the `GetRole` response. See:
8646
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role-return-values
8647
+ *
8648
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
8649
+ * substituted into sibling references.
8650
+ */
8651
+ async getAttribute(physicalId, _resourceType, attributeName) {
8652
+ try {
8653
+ const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));
8654
+ switch (attributeName) {
8655
+ case "Arn":
8656
+ return resp.Role?.Arn;
8657
+ case "RoleId":
8658
+ return resp.Role?.RoleId;
8659
+ default:
8660
+ return void 0;
8661
+ }
8662
+ } catch (err) {
8663
+ if (err instanceof NoSuchEntityException)
8664
+ return void 0;
8665
+ throw err;
8666
+ }
8667
+ }
8588
8668
  /**
8589
8669
  * Adopt an existing IAM role into cdkd state.
8590
8670
  *
@@ -10435,17 +10515,35 @@ var S3BucketProvider = class {
10435
10515
  return region || "us-east-1";
10436
10516
  }
10437
10517
  /**
10438
- * Build attributes for an S3 bucket
10518
+ * Build attributes for an S3 bucket.
10519
+ *
10520
+ * Covers every CloudFormation `Fn::GetAtt` return value for
10521
+ * `AWS::S3::Bucket`. All fields are derivable from `bucketName` + region —
10522
+ * no extra AWS API call is needed. See:
10523
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#aws-properties-s3-bucket-return-values
10439
10524
  */
10440
10525
  async buildAttributes(bucketName) {
10441
10526
  const region = await this.getRegion();
10442
10527
  return {
10443
10528
  Arn: `arn:aws:s3:::${bucketName}`,
10444
10529
  DomainName: `${bucketName}.s3.amazonaws.com`,
10530
+ DualStackDomainName: `${bucketName}.s3.dualstack.${region}.amazonaws.com`,
10445
10531
  RegionalDomainName: `${bucketName}.s3.${region}.amazonaws.com`,
10446
10532
  WebsiteURL: `http://${bucketName}.s3-website-${region}.amazonaws.com`
10447
10533
  };
10448
10534
  }
10535
+ /**
10536
+ * Resolve a single `Fn::GetAtt` attribute for an existing bucket.
10537
+ *
10538
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
10539
+ * substituted into sibling references. All S3 Bucket attributes are
10540
+ * derivable from bucket name + region, so this avoids the round trip and
10541
+ * reuses the same templating as `buildAttributes`.
10542
+ */
10543
+ async getAttribute(physicalId, _resourceType, attributeName) {
10544
+ const attrs = await this.buildAttributes(physicalId);
10545
+ return attrs[attributeName];
10546
+ }
10449
10547
  /**
10450
10548
  * Apply versioning configuration if specified
10451
10549
  */
@@ -11740,6 +11838,43 @@ var SQSQueueProvider = class {
11740
11838
  return `arn:aws:sqs:unknown:unknown:${queueName}`;
11741
11839
  }
11742
11840
  }
11841
+ /**
11842
+ * Resolve a single `Fn::GetAtt` attribute for an existing SQS queue.
11843
+ *
11844
+ * CloudFormation's `AWS::SQS::Queue` exposes `Arn`, `QueueName` and
11845
+ * `QueueUrl`. The cdkd physicalId is the queue URL; `QueueUrl` and
11846
+ * `QueueName` are derivable from it without an AWS call, while `Arn`
11847
+ * requires `GetQueueAttributes`. See:
11848
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html#aws-properties-sqs-queues-return-values
11849
+ *
11850
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
11851
+ * substituted into sibling references.
11852
+ */
11853
+ async getAttribute(physicalId, _resourceType, attributeName) {
11854
+ switch (attributeName) {
11855
+ case "QueueUrl":
11856
+ return physicalId;
11857
+ case "QueueName":
11858
+ return physicalId.substring(physicalId.lastIndexOf("/") + 1);
11859
+ case "Arn": {
11860
+ try {
11861
+ const resp = await this.sqsClient.send(
11862
+ new GetQueueAttributesCommand({
11863
+ QueueUrl: physicalId,
11864
+ AttributeNames: ["QueueArn"]
11865
+ })
11866
+ );
11867
+ return resp.Attributes?.["QueueArn"];
11868
+ } catch (err) {
11869
+ if (err instanceof QueueDoesNotExist)
11870
+ return void 0;
11871
+ throw err;
11872
+ }
11873
+ }
11874
+ default:
11875
+ return void 0;
11876
+ }
11877
+ }
11743
11878
  /**
11744
11879
  * Adopt an existing SQS queue into cdkd state.
11745
11880
  *
@@ -12275,6 +12410,28 @@ var SNSTopicProvider = class {
12275
12410
  );
12276
12411
  }
12277
12412
  }
12413
+ /**
12414
+ * Resolve a single `Fn::GetAtt` attribute for an existing SNS topic.
12415
+ *
12416
+ * CloudFormation's `AWS::SNS::Topic` exposes `TopicName` and `TopicArn`.
12417
+ * The cdkd physicalId is the topic ARN, so both are derivable without
12418
+ * an AWS call. See:
12419
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html#aws-properties-sns-topic-return-values
12420
+ *
12421
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
12422
+ * substituted into sibling references.
12423
+ */
12424
+ // eslint-disable-next-line @typescript-eslint/require-await -- consistent async signature with other providers
12425
+ async getAttribute(physicalId, _resourceType, attributeName) {
12426
+ switch (attributeName) {
12427
+ case "TopicArn":
12428
+ return physicalId;
12429
+ case "TopicName":
12430
+ return physicalId.split(":").pop();
12431
+ default:
12432
+ return void 0;
12433
+ }
12434
+ }
12278
12435
  /**
12279
12436
  * Adopt an existing SNS topic into cdkd state.
12280
12437
  *
@@ -13256,6 +13413,43 @@ var LambdaFunctionProvider = class {
13256
13413
  }
13257
13414
  return (crc ^ 4294967295) >>> 0;
13258
13415
  }
13416
+ /**
13417
+ * Resolve a single `Fn::GetAtt` attribute for an existing Lambda function.
13418
+ *
13419
+ * CloudFormation's `AWS::Lambda::Function` exposes `Arn`,
13420
+ * `SnapStartResponse.ApplyOn`, and `SnapStartResponse.OptimizationStatus`
13421
+ * as documented at
13422
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#aws-resource-lambda-function-return-values.
13423
+ *
13424
+ * All three live in the same `GetFunction` response (`Configuration.FunctionArn`
13425
+ * and `Configuration.SnapStart.{ApplyOn,OptimizationStatus}`), so a single API
13426
+ * call covers every supported attr. Used by `cdkd orphan` to live-fetch
13427
+ * attribute values that need to be substituted into sibling references.
13428
+ */
13429
+ async getAttribute(physicalId, _resourceType, attributeName) {
13430
+ if (attributeName !== "Arn" && attributeName !== "SnapStartResponse.ApplyOn" && attributeName !== "SnapStartResponse.OptimizationStatus") {
13431
+ return void 0;
13432
+ }
13433
+ try {
13434
+ const resp = await this.lambdaClient.send(
13435
+ new GetFunctionCommand({ FunctionName: physicalId })
13436
+ );
13437
+ switch (attributeName) {
13438
+ case "Arn":
13439
+ return resp.Configuration?.FunctionArn;
13440
+ case "SnapStartResponse.ApplyOn":
13441
+ return resp.Configuration?.SnapStart?.ApplyOn;
13442
+ case "SnapStartResponse.OptimizationStatus":
13443
+ return resp.Configuration?.SnapStart?.OptimizationStatus;
13444
+ default:
13445
+ return void 0;
13446
+ }
13447
+ } catch (err) {
13448
+ if (err instanceof ResourceNotFoundException)
13449
+ return void 0;
13450
+ throw err;
13451
+ }
13452
+ }
13259
13453
  /**
13260
13454
  * Adopt an existing Lambda function into cdkd state.
13261
13455
  *
@@ -13528,6 +13722,7 @@ var LambdaPermissionProvider = class {
13528
13722
  import {
13529
13723
  CreateFunctionUrlConfigCommand,
13530
13724
  DeleteFunctionUrlConfigCommand,
13725
+ GetFunctionUrlConfigCommand as GetFunctionUrlConfigCommand2,
13531
13726
  UpdateFunctionUrlConfigCommand,
13532
13727
  ResourceNotFoundException as ResourceNotFoundException3
13533
13728
  } from "@aws-sdk/client-lambda";
@@ -13655,6 +13850,36 @@ var LambdaUrlProvider = class {
13655
13850
  );
13656
13851
  }
13657
13852
  }
13853
+ /**
13854
+ * Resolve a single `Fn::GetAtt` attribute for an existing Lambda Function
13855
+ * URL.
13856
+ *
13857
+ * CloudFormation's `AWS::Lambda::Url` exposes `FunctionArn` and
13858
+ * `FunctionUrl`. Both come from `GetFunctionUrlConfig`. See:
13859
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-url.html#aws-resource-lambda-url-return-values
13860
+ *
13861
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
13862
+ * substituted into sibling references.
13863
+ */
13864
+ async getAttribute(physicalId, _resourceType, attributeName) {
13865
+ try {
13866
+ const resp = await this.lambdaClient.send(
13867
+ new GetFunctionUrlConfigCommand2({ FunctionName: physicalId })
13868
+ );
13869
+ switch (attributeName) {
13870
+ case "FunctionArn":
13871
+ return resp.FunctionArn;
13872
+ case "FunctionUrl":
13873
+ return resp.FunctionUrl;
13874
+ default:
13875
+ return void 0;
13876
+ }
13877
+ } catch (err) {
13878
+ if (err instanceof ResourceNotFoundException3)
13879
+ return void 0;
13880
+ throw err;
13881
+ }
13882
+ }
13658
13883
  /**
13659
13884
  * Adopt an existing Lambda Function URL into cdkd state.
13660
13885
  *
@@ -14366,6 +14591,40 @@ var DynamoDBTableProvider = class {
14366
14591
  }
14367
14592
  throw new Error(`Table ${tableName} did not reach ACTIVE status within ${maxAttempts} seconds`);
14368
14593
  }
14594
+ /**
14595
+ * Resolve a single `Fn::GetAtt` attribute for an existing DynamoDB table.
14596
+ *
14597
+ * CloudFormation's `AWS::DynamoDB::Table` exposes `Arn`, `StreamArn`
14598
+ * (a.k.a. `LatestStreamArn` in the SDK; CFn returns the latest enabled
14599
+ * stream's ARN), and `LatestStreamLabel`. All three are sibling fields on
14600
+ * the same `DescribeTable` response, so a single API call covers every
14601
+ * supported attr. See:
14602
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#aws-resource-dynamodb-table-return-values
14603
+ *
14604
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
14605
+ * substituted into sibling references.
14606
+ */
14607
+ async getAttribute(physicalId, _resourceType, attributeName) {
14608
+ try {
14609
+ const resp = await this.dynamoDBClient.send(
14610
+ new DescribeTableCommand2({ TableName: physicalId })
14611
+ );
14612
+ switch (attributeName) {
14613
+ case "Arn":
14614
+ return resp.Table?.TableArn;
14615
+ case "StreamArn":
14616
+ return resp.Table?.LatestStreamArn;
14617
+ case "LatestStreamLabel":
14618
+ return resp.Table?.LatestStreamLabel;
14619
+ default:
14620
+ return void 0;
14621
+ }
14622
+ } catch (err) {
14623
+ if (err instanceof ResourceNotFoundException6)
14624
+ return void 0;
14625
+ throw err;
14626
+ }
14627
+ }
14369
14628
  /**
14370
14629
  * Adopt an existing DynamoDB table into cdkd state.
14371
14630
  *
@@ -14653,6 +14912,23 @@ var LogsLogGroupProvider = class {
14653
14912
  return `arn:aws:logs:unknown:unknown:log-group:${logGroupName}:*`;
14654
14913
  }
14655
14914
  }
14915
+ /**
14916
+ * Resolve a single `Fn::GetAtt` attribute for an existing log group.
14917
+ *
14918
+ * CloudFormation's `AWS::Logs::LogGroup` exposes only `Arn`. The ARN is
14919
+ * derivable from the log group name + account + region via the existing
14920
+ * `buildArn` helper. See:
14921
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#aws-resource-logs-loggroup-return-values
14922
+ *
14923
+ * Used by `cdkd orphan` to live-fetch attribute values that need to be
14924
+ * substituted into sibling references.
14925
+ */
14926
+ async getAttribute(physicalId, _resourceType, attributeName) {
14927
+ if (attributeName !== "Arn") {
14928
+ return void 0;
14929
+ }
14930
+ return this.buildArn(physicalId);
14931
+ }
14656
14932
  /**
14657
14933
  * Adopt an existing CloudWatch Logs log group into cdkd state.
14658
14934
  *
@@ -16690,29 +16966,63 @@ var EC2Provider = class {
16690
16966
  }
16691
16967
  }
16692
16968
  }
16969
+ /**
16970
+ * Resolve a single `Fn::GetAtt` attribute for an `AWS::EC2::VPC`.
16971
+ *
16972
+ * CloudFormation returns `CidrBlock`, `CidrBlockAssociations`,
16973
+ * `DefaultNetworkAcl`, `DefaultSecurityGroup`, and `Ipv6CidrBlocks`. See:
16974
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html#aws-resource-ec2-vpc-return-values
16975
+ *
16976
+ * `DefaultNetworkAcl` and `DefaultSecurityGroup` previously returned wrong
16977
+ * values (DHCP options id and `undefined` respectively); the AWS console
16978
+ * surfaces these the same way as CFn — by filtering the relevant
16979
+ * `Describe*` API on `vpc-id` + the `default` flag.
16980
+ */
16693
16981
  async getVpcAttribute(physicalId, attributeName) {
16694
- if (attributeName === "VpcId")
16695
- return physicalId;
16696
16982
  try {
16697
- const response = await this.ec2Client.send(new DescribeVpcsCommand2({ VpcIds: [physicalId] }));
16698
- const vpc = response.Vpcs?.[0];
16699
- if (!vpc)
16700
- return void 0;
16701
16983
  switch (attributeName) {
16702
- case "CidrBlock":
16703
- return vpc.CidrBlock;
16704
- case "Ipv6CidrBlocks":
16705
- return vpc.Ipv6CidrBlockAssociationSet?.filter(
16706
- (a) => a.Ipv6CidrBlockState?.State === "associated"
16707
- ).map((a) => a.Ipv6CidrBlock) || [];
16708
- case "CidrBlockAssociations":
16709
- return vpc.CidrBlockAssociationSet?.map((a) => a.AssociationId) || [];
16710
- case "DefaultNetworkAcl":
16711
- return vpc.DhcpOptionsId;
16712
- case "DefaultSecurityGroup":
16713
- return void 0;
16714
- default:
16715
- return void 0;
16984
+ case "DefaultNetworkAcl": {
16985
+ const resp = await this.ec2Client.send(
16986
+ new DescribeNetworkAclsCommand({
16987
+ Filters: [
16988
+ { Name: "vpc-id", Values: [physicalId] },
16989
+ { Name: "default", Values: ["true"] }
16990
+ ]
16991
+ })
16992
+ );
16993
+ return resp.NetworkAcls?.[0]?.NetworkAclId;
16994
+ }
16995
+ case "DefaultSecurityGroup": {
16996
+ const resp = await this.ec2Client.send(
16997
+ new DescribeSecurityGroupsCommand2({
16998
+ Filters: [
16999
+ { Name: "vpc-id", Values: [physicalId] },
17000
+ { Name: "group-name", Values: ["default"] }
17001
+ ]
17002
+ })
17003
+ );
17004
+ return resp.SecurityGroups?.[0]?.GroupId;
17005
+ }
17006
+ default: {
17007
+ const response = await this.ec2Client.send(
17008
+ new DescribeVpcsCommand2({ VpcIds: [physicalId] })
17009
+ );
17010
+ const vpc = response.Vpcs?.[0];
17011
+ if (!vpc)
17012
+ return void 0;
17013
+ switch (attributeName) {
17014
+ case "CidrBlock":
17015
+ return vpc.CidrBlock;
17016
+ case "Ipv6CidrBlocks":
17017
+ return vpc.Ipv6CidrBlockAssociationSet?.filter(
17018
+ (a) => a.Ipv6CidrBlockState?.State === "associated"
17019
+ ).map((a) => a.Ipv6CidrBlock) || [];
17020
+ case "CidrBlockAssociations":
17021
+ return vpc.CidrBlockAssociationSet?.map((a) => a.AssociationId) || [];
17022
+ default:
17023
+ return void 0;
17024
+ }
17025
+ }
16716
17026
  }
16717
17027
  } catch {
16718
17028
  return void 0;
@@ -31580,8 +31890,8 @@ var DeployEngine = class {
31580
31890
  const baseLabel = `${verb} ${logicalId} (${resourceType})`;
31581
31891
  renderer.addTask(logicalId, baseLabel);
31582
31892
  const operationKind = change.changeType === "CREATE" ? "CREATE" : change.changeType === "DELETE" ? "DELETE" : "UPDATE";
31583
- const warnAfterMs = this.options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
31584
- const timeoutMs = this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
31893
+ const warnAfterMs = this.options.resourceWarnAfterByType?.[resourceType] ?? this.options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
31894
+ const timeoutMs = this.options.resourceTimeoutByType?.[resourceType] ?? this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
31585
31895
  try {
31586
31896
  await withResourceDeadline(
31587
31897
  async () => {
@@ -32085,8 +32395,8 @@ async function deployCommand(stacks, options) {
32085
32395
  }
32086
32396
  warnIfDeprecatedRegion(options);
32087
32397
  validateResourceTimeouts({
32088
- resourceWarnAfter: options.resourceWarnAfter,
32089
- resourceTimeout: options.resourceTimeout
32398
+ ...options.resourceWarnAfter && { resourceWarnAfter: options.resourceWarnAfter },
32399
+ ...options.resourceTimeout && { resourceTimeout: options.resourceTimeout }
32090
32400
  });
32091
32401
  if (!options.wait) {
32092
32402
  process.env["CDKD_NO_WAIT"] = "true";
@@ -32281,8 +32591,18 @@ Deploying stack: ${stackInfo.stackName}${stackRegion !== baseRegion ? ` (region:
32281
32591
  concurrency: options.concurrency,
32282
32592
  dryRun: options.dryRun,
32283
32593
  noRollback: !options.rollback,
32284
- resourceWarnAfterMs: options.resourceWarnAfter,
32285
- resourceTimeoutMs: options.resourceTimeout
32594
+ ...options.resourceWarnAfter?.globalMs !== void 0 && {
32595
+ resourceWarnAfterMs: options.resourceWarnAfter.globalMs
32596
+ },
32597
+ ...options.resourceTimeout?.globalMs !== void 0 && {
32598
+ resourceTimeoutMs: options.resourceTimeout.globalMs
32599
+ },
32600
+ ...options.resourceWarnAfter?.perTypeMs && {
32601
+ resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs
32602
+ },
32603
+ ...options.resourceTimeout?.perTypeMs && {
32604
+ resourceTimeoutByType: options.resourceTimeout.perTypeMs
32605
+ }
32286
32606
  },
32287
32607
  stackRegion
32288
32608
  );
@@ -32692,8 +33012,6 @@ Acquiring lock for stack ${stackName}...`);
32692
33012
  logger.debug(
32693
33013
  `Deletion level ${executionLevels.length - levelIndex}/${executionLevels.length} (${level.length} resources)`
32694
33014
  );
32695
- const warnAfterMs = ctx.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
32696
- const timeoutMs = ctx.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
32697
33015
  const stackRegion2 = state.region ?? ctx.baseRegion;
32698
33016
  const deletePromises = level.map(async (logicalId) => {
32699
33017
  const resource = state.resources[logicalId];
@@ -32701,6 +33019,8 @@ Acquiring lock for stack ${stackName}...`);
32701
33019
  logger.warn(`Resource ${logicalId} not found in state, skipping`);
32702
33020
  return;
32703
33021
  }
33022
+ const warnAfterMs = ctx.resourceWarnAfterByType?.[resource.resourceType] ?? ctx.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
33023
+ const timeoutMs = ctx.resourceTimeoutByType?.[resource.resourceType] ?? ctx.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
32704
33024
  const baseLabel = `Deleting ${logicalId} (${resource.resourceType})`;
32705
33025
  renderer.addTask(logicalId, baseLabel);
32706
33026
  try {
@@ -32821,8 +33141,8 @@ async function destroyCommand(stackArgs, options) {
32821
33141
  }
32822
33142
  warnIfDeprecatedRegion(options);
32823
33143
  validateResourceTimeouts({
32824
- resourceWarnAfter: options.resourceWarnAfter,
32825
- resourceTimeout: options.resourceTimeout
33144
+ ...options.resourceWarnAfter && { resourceWarnAfter: options.resourceWarnAfter },
33145
+ ...options.resourceTimeout && { resourceTimeout: options.resourceTimeout }
32826
33146
  });
32827
33147
  const region = options.region || process.env["AWS_REGION"] || "us-east-1";
32828
33148
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
@@ -32956,8 +33276,18 @@ Preparing to destroy stack: ${stackName}`);
32956
33276
  ...options.profile && { profile: options.profile },
32957
33277
  stateBucket,
32958
33278
  skipConfirmation: options.yes || options.force,
32959
- resourceWarnAfterMs: options.resourceWarnAfter,
32960
- resourceTimeoutMs: options.resourceTimeout
33279
+ ...options.resourceWarnAfter?.globalMs !== void 0 && {
33280
+ resourceWarnAfterMs: options.resourceWarnAfter.globalMs
33281
+ },
33282
+ ...options.resourceTimeout?.globalMs !== void 0 && {
33283
+ resourceTimeoutMs: options.resourceTimeout.globalMs
33284
+ },
33285
+ ...options.resourceWarnAfter?.perTypeMs && {
33286
+ resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs
33287
+ },
33288
+ ...options.resourceTimeout?.perTypeMs && {
33289
+ resourceTimeoutByType: options.resourceTimeout.perTypeMs
33290
+ }
32961
33291
  });
32962
33292
  }
32963
33293
  } finally {
@@ -32986,15 +33316,432 @@ function createDestroyCommand() {
32986
33316
  import * as readline2 from "node:readline/promises";
32987
33317
  import { Command as Command7 } from "commander";
32988
33318
  init_aws_clients();
32989
- async function orphanCommand(stackArgs, options) {
33319
+
33320
+ // src/cli/cdk-path.ts
33321
+ function readCdkPath(resource) {
33322
+ const meta = resource.Metadata;
33323
+ if (!meta)
33324
+ return "";
33325
+ const v = meta["aws:cdk:path"];
33326
+ return typeof v === "string" ? v : "";
33327
+ }
33328
+ function buildCdkPathIndex(template) {
33329
+ const index = /* @__PURE__ */ new Map();
33330
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
33331
+ const path = readCdkPath(resource);
33332
+ if (path)
33333
+ index.set(path, logicalId);
33334
+ }
33335
+ return index;
33336
+ }
33337
+
33338
+ // src/analyzer/orphan-rewriter.ts
33339
+ var AttributeFetcher = class {
33340
+ constructor(orphans, providerRegistry, options) {
33341
+ this.orphans = orphans;
33342
+ this.providerRegistry = providerRegistry;
33343
+ this.options = options;
33344
+ }
33345
+ cache = /* @__PURE__ */ new Map();
33346
+ logger = getLogger().child("OrphanRewriter");
33347
+ /**
33348
+ * Return the orphan's resolved value for `Ref` (its physicalId) — never
33349
+ * needs an AWS call.
33350
+ */
33351
+ ref(orphanLogicalId) {
33352
+ const o = this.orphans[orphanLogicalId];
33353
+ if (!o) {
33354
+ throw new Error(
33355
+ `Internal: Ref to '${orphanLogicalId}' has no orphan entry \u2014 should have been filtered out`
33356
+ );
33357
+ }
33358
+ return o.physicalId;
33359
+ }
33360
+ /**
33361
+ * Return the orphan's resolved value for `Fn::GetAtt`. Hits the live
33362
+ * provider on first call; subsequent calls reuse the cached result.
33363
+ *
33364
+ * Returns `{ ok: true, value }` on success; `{ ok: false, reason }`
33365
+ * when the live fetch failed AND the `--force` cache fallback either
33366
+ * was disabled or also lacked the attribute. In the cache-fallback
33367
+ * success path returns `{ ok: true, value, fromCache: true }`.
33368
+ */
33369
+ async getAtt(orphanLogicalId, attribute) {
33370
+ const cacheKey = `${orphanLogicalId}\0${attribute}`;
33371
+ if (this.cache.has(cacheKey)) {
33372
+ return { ok: true, value: this.cache.get(cacheKey) };
33373
+ }
33374
+ const orphan = this.orphans[orphanLogicalId];
33375
+ if (!orphan) {
33376
+ return {
33377
+ ok: false,
33378
+ reason: `Internal: GetAtt to '${orphanLogicalId}' has no orphan entry`
33379
+ };
33380
+ }
33381
+ let provider;
33382
+ try {
33383
+ provider = this.providerRegistry.getProvider(orphan.resourceType);
33384
+ } catch (err) {
33385
+ return {
33386
+ ok: false,
33387
+ reason: `no provider available for ${orphan.resourceType}: ${err instanceof Error ? err.message : String(err)}`
33388
+ };
33389
+ }
33390
+ if (!provider.getAttribute) {
33391
+ return this.cacheFallback(
33392
+ orphanLogicalId,
33393
+ attribute,
33394
+ `provider for ${orphan.resourceType} does not implement getAttribute`
33395
+ );
33396
+ }
33397
+ try {
33398
+ const value = await provider.getAttribute(orphan.physicalId, orphan.resourceType, attribute);
33399
+ if (value === void 0) {
33400
+ return this.cacheFallback(
33401
+ orphanLogicalId,
33402
+ attribute,
33403
+ `provider returned undefined for ${orphan.resourceType}.${attribute}`
33404
+ );
33405
+ }
33406
+ this.cache.set(cacheKey, value);
33407
+ return { ok: true, value };
33408
+ } catch (err) {
33409
+ return this.cacheFallback(
33410
+ orphanLogicalId,
33411
+ attribute,
33412
+ err instanceof Error ? err.message : String(err)
33413
+ );
33414
+ }
33415
+ }
33416
+ /**
33417
+ * Try the orphan's `state.attributes[attribute]` as a last-resort value
33418
+ * source under `--force`. Without `--force`, returns the original
33419
+ * failure reason unchanged (caller pushes to `unresolvable`).
33420
+ */
33421
+ cacheFallback(orphanLogicalId, attribute, reason) {
33422
+ if (!this.options.force) {
33423
+ return { ok: false, reason };
33424
+ }
33425
+ const orphan = this.orphans[orphanLogicalId];
33426
+ const cached = orphan.attributes?.[attribute];
33427
+ if (cached === void 0) {
33428
+ this.logger.warn(
33429
+ `--force: state.attributes also lacks '${orphanLogicalId}.${attribute}'; leaving the original intrinsic in place.`
33430
+ );
33431
+ return {
33432
+ ok: false,
33433
+ reason: `${reason}; state.attributes cache also has no value for '${attribute}'`
33434
+ };
33435
+ }
33436
+ this.logger.warn(
33437
+ `--force: live fetch failed for '${orphanLogicalId}.${attribute}' (${reason}); falling back to cached value from state.attributes.`
33438
+ );
33439
+ const cacheKey = `${orphanLogicalId}\0${attribute}`;
33440
+ this.cache.set(cacheKey, cached);
33441
+ return { ok: true, value: cached, fromCache: true };
33442
+ }
33443
+ };
33444
+ async function rewriteResourceReferences(state, orphanLogicalIds, providerRegistry, options = {}) {
33445
+ const orphanSet = new Set(orphanLogicalIds);
33446
+ const orphans = {};
33447
+ for (const id of orphanLogicalIds) {
33448
+ const r = state.resources[id];
33449
+ if (!r) {
33450
+ throw new Error(`rewriteResourceReferences: orphan '${id}' not found in state.resources`);
33451
+ }
33452
+ orphans[id] = r;
33453
+ }
33454
+ const fetcher = new AttributeFetcher(orphans, providerRegistry, options);
33455
+ const rewrites = [];
33456
+ const unresolvable = [];
33457
+ const newResources = {};
33458
+ for (const [logicalId, resource] of Object.entries(state.resources)) {
33459
+ if (orphanSet.has(logicalId))
33460
+ continue;
33461
+ const rewrittenProperties = await rewriteValue(
33462
+ resource.properties,
33463
+ `properties`,
33464
+ logicalId,
33465
+ orphanSet,
33466
+ fetcher,
33467
+ rewrites,
33468
+ unresolvable
33469
+ );
33470
+ const rewrittenAttributes = resource.attributes ? await rewriteValue(
33471
+ resource.attributes,
33472
+ `attributes`,
33473
+ logicalId,
33474
+ orphanSet,
33475
+ fetcher,
33476
+ rewrites,
33477
+ unresolvable
33478
+ ) : void 0;
33479
+ const newDeps = (resource.dependencies ?? []).filter((dep) => {
33480
+ if (orphanSet.has(dep)) {
33481
+ rewrites.push({
33482
+ logicalId,
33483
+ path: "dependencies",
33484
+ kind: "dependency",
33485
+ before: dep,
33486
+ after: null,
33487
+ orphanLogicalId: dep
33488
+ });
33489
+ return false;
33490
+ }
33491
+ return true;
33492
+ });
33493
+ newResources[logicalId] = {
33494
+ ...resource,
33495
+ properties: rewrittenProperties,
33496
+ ...rewrittenAttributes !== void 0 && {
33497
+ attributes: rewrittenAttributes
33498
+ },
33499
+ dependencies: newDeps
33500
+ };
33501
+ }
33502
+ const newOutputs = {};
33503
+ for (const [name, value] of Object.entries(state.outputs ?? {})) {
33504
+ newOutputs[name] = await rewriteValue(
33505
+ value,
33506
+ `outputs.${name}`,
33507
+ `<output:${name}>`,
33508
+ orphanSet,
33509
+ fetcher,
33510
+ rewrites,
33511
+ unresolvable
33512
+ );
33513
+ }
33514
+ return {
33515
+ state: {
33516
+ ...state,
33517
+ resources: newResources,
33518
+ outputs: newOutputs,
33519
+ lastModified: Date.now()
33520
+ },
33521
+ rewrites,
33522
+ unresolvable
33523
+ };
33524
+ }
33525
+ async function rewriteValue(value, pathPrefix, ownerLogicalId, orphanSet, fetcher, rewrites, unresolvable) {
33526
+ if (typeof value !== "object" || value === null)
33527
+ return value;
33528
+ if (Array.isArray(value)) {
33529
+ const out2 = [];
33530
+ for (let i = 0; i < value.length; i++) {
33531
+ out2.push(
33532
+ await rewriteValue(
33533
+ value[i],
33534
+ `${pathPrefix}[${i}]`,
33535
+ ownerLogicalId,
33536
+ orphanSet,
33537
+ fetcher,
33538
+ rewrites,
33539
+ unresolvable
33540
+ )
33541
+ );
33542
+ }
33543
+ return out2;
33544
+ }
33545
+ const obj = value;
33546
+ if ("Ref" in obj && Object.keys(obj).length === 1 && typeof obj["Ref"] === "string") {
33547
+ const target = obj["Ref"];
33548
+ if (orphanSet.has(target)) {
33549
+ const replaced = fetcher.ref(target);
33550
+ rewrites.push({
33551
+ logicalId: ownerLogicalId,
33552
+ path: pathPrefix,
33553
+ kind: "ref",
33554
+ before: { Ref: target },
33555
+ after: replaced,
33556
+ orphanLogicalId: target
33557
+ });
33558
+ return replaced;
33559
+ }
33560
+ return value;
33561
+ }
33562
+ if ("Fn::GetAtt" in obj && Object.keys(obj).length === 1) {
33563
+ const arg = obj["Fn::GetAtt"];
33564
+ let target;
33565
+ let attribute;
33566
+ if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
33567
+ target = arg[0];
33568
+ attribute = arg[1];
33569
+ } else if (typeof arg === "string") {
33570
+ const dot = arg.indexOf(".");
33571
+ if (dot > 0) {
33572
+ target = arg.slice(0, dot);
33573
+ attribute = arg.slice(dot + 1);
33574
+ }
33575
+ }
33576
+ if (target && attribute && orphanSet.has(target)) {
33577
+ const result = await fetcher.getAtt(target, attribute);
33578
+ if (result.ok) {
33579
+ rewrites.push({
33580
+ logicalId: ownerLogicalId,
33581
+ path: pathPrefix,
33582
+ kind: "getAtt",
33583
+ before: { "Fn::GetAtt": [target, attribute] },
33584
+ after: result.value,
33585
+ orphanLogicalId: target
33586
+ });
33587
+ return result.value;
33588
+ }
33589
+ unresolvable.push({
33590
+ logicalId: ownerLogicalId,
33591
+ path: pathPrefix,
33592
+ orphanLogicalId: target,
33593
+ attribute,
33594
+ reason: result.reason
33595
+ });
33596
+ return value;
33597
+ }
33598
+ return value;
33599
+ }
33600
+ if ("Fn::Sub" in obj && Object.keys(obj).length === 1) {
33601
+ const arg = obj["Fn::Sub"];
33602
+ let template;
33603
+ let varMap;
33604
+ if (typeof arg === "string") {
33605
+ template = arg;
33606
+ } else if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "object" && arg[1] !== null) {
33607
+ template = arg[0];
33608
+ varMap = arg[1];
33609
+ }
33610
+ if (template !== void 0) {
33611
+ const { rewritten, didChange, hasUnresolvable } = await rewriteSubTemplate(
33612
+ template,
33613
+ ownerLogicalId,
33614
+ pathPrefix,
33615
+ orphanSet,
33616
+ fetcher,
33617
+ rewrites,
33618
+ unresolvable,
33619
+ varMap
33620
+ );
33621
+ if (didChange) {
33622
+ const stillHasIntrinsics = /\$\{[^}]+\}/.test(rewritten);
33623
+ if (varMap && stillHasIntrinsics) {
33624
+ return { "Fn::Sub": [rewritten, varMap] };
33625
+ }
33626
+ if (stillHasIntrinsics) {
33627
+ return { "Fn::Sub": rewritten };
33628
+ }
33629
+ return rewritten;
33630
+ }
33631
+ return value;
33632
+ }
33633
+ return value;
33634
+ }
33635
+ const out = {};
33636
+ for (const [k, v] of Object.entries(obj)) {
33637
+ out[k] = await rewriteValue(
33638
+ v,
33639
+ pathPrefix === "" ? k : `${pathPrefix}.${k}`,
33640
+ ownerLogicalId,
33641
+ orphanSet,
33642
+ fetcher,
33643
+ rewrites,
33644
+ unresolvable
33645
+ );
33646
+ }
33647
+ return out;
33648
+ }
33649
+ async function rewriteSubTemplate(template, ownerLogicalId, pathPrefix, orphanSet, fetcher, rewrites, unresolvable, varMap) {
33650
+ const placeholderRe = /\$\{([^}]+)\}/g;
33651
+ const matches = [...template.matchAll(placeholderRe)];
33652
+ if (matches.length === 0) {
33653
+ return { rewritten: template, didChange: false, hasUnresolvable: false };
33654
+ }
33655
+ let didChange = false;
33656
+ let hasUnresolvable = false;
33657
+ let cursor = 0;
33658
+ let out = "";
33659
+ for (const m of matches) {
33660
+ const inner = m[1] ?? "";
33661
+ const start = m.index ?? 0;
33662
+ out += template.slice(cursor, start);
33663
+ cursor = start + m[0].length;
33664
+ if (varMap && inner in varMap) {
33665
+ out += m[0];
33666
+ continue;
33667
+ }
33668
+ const dot = inner.indexOf(".");
33669
+ if (dot < 0) {
33670
+ if (orphanSet.has(inner)) {
33671
+ const replaced = fetcher.ref(inner);
33672
+ rewrites.push({
33673
+ logicalId: ownerLogicalId,
33674
+ path: pathPrefix,
33675
+ kind: "sub",
33676
+ before: m[0],
33677
+ after: replaced,
33678
+ orphanLogicalId: inner
33679
+ });
33680
+ out += replaced;
33681
+ didChange = true;
33682
+ } else {
33683
+ out += m[0];
33684
+ }
33685
+ } else {
33686
+ const target = inner.slice(0, dot);
33687
+ const attribute = inner.slice(dot + 1);
33688
+ if (orphanSet.has(target)) {
33689
+ const result = await fetcher.getAtt(target, attribute);
33690
+ if (result.ok) {
33691
+ const stringified = String(result.value);
33692
+ rewrites.push({
33693
+ logicalId: ownerLogicalId,
33694
+ path: pathPrefix,
33695
+ kind: "sub",
33696
+ before: m[0],
33697
+ after: stringified,
33698
+ orphanLogicalId: target
33699
+ });
33700
+ out += stringified;
33701
+ didChange = true;
33702
+ } else {
33703
+ unresolvable.push({
33704
+ logicalId: ownerLogicalId,
33705
+ path: pathPrefix,
33706
+ orphanLogicalId: target,
33707
+ attribute,
33708
+ reason: result.reason
33709
+ });
33710
+ out += m[0];
33711
+ hasUnresolvable = true;
33712
+ }
33713
+ } else {
33714
+ out += m[0];
33715
+ }
33716
+ }
33717
+ }
33718
+ out += template.slice(cursor);
33719
+ return { rewritten: out, didChange, hasUnresolvable };
33720
+ }
33721
+
33722
+ // src/cli/commands/orphan.ts
33723
+ async function orphanCommand(pathArgs, options) {
32990
33724
  const logger = getLogger();
32991
33725
  if (options.verbose)
32992
33726
  logger.setLevel("debug");
32993
33727
  warnIfDeprecatedRegion(options);
33728
+ if (pathArgs.length === 0) {
33729
+ throw new Error(
33730
+ "'cdkd orphan' requires at least one construct path, e.g. 'cdkd orphan MyStack/MyTable'.\n To remove a stack's state record (the previous behavior), use:\n cdkd state orphan MyStack"
33731
+ );
33732
+ }
33733
+ for (const p of pathArgs) {
33734
+ if (!p.includes("/")) {
33735
+ throw new Error(
33736
+ `'cdkd orphan' now expects a construct path like 'MyStack/MyTable'.
33737
+ Got: '${p}'
33738
+ To remove a stack's state record (the previous behavior), use:
33739
+ cdkd state orphan ${p}`
33740
+ );
33741
+ }
33742
+ }
32994
33743
  const region = options.region || process.env["AWS_REGION"] || "us-east-1";
32995
33744
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
32996
- logger.info("Starting stack orphan...");
32997
- logger.debug("Options:", options);
32998
33745
  if (options.region) {
32999
33746
  process.env["AWS_REGION"] = options.region;
33000
33747
  process.env["AWS_DEFAULT_REGION"] = options.region;
@@ -33005,10 +33752,7 @@ async function orphanCommand(stackArgs, options) {
33005
33752
  });
33006
33753
  setAwsClients(awsClients);
33007
33754
  try {
33008
- const stateConfig = {
33009
- bucket: stateBucket,
33010
- prefix: options.statePrefix
33011
- };
33755
+ const stateConfig = { bucket: stateBucket, prefix: options.statePrefix };
33012
33756
  const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
33013
33757
  ...options.region && { region: options.region },
33014
33758
  ...options.profile && { profile: options.profile }
@@ -33016,150 +33760,245 @@ async function orphanCommand(stackArgs, options) {
33016
33760
  await stateBackend.verifyBucketExists();
33017
33761
  const lockManager = new LockManager(awsClients.s3, stateConfig);
33018
33762
  const appCmd = options.app || resolveApp();
33019
- let appStacks = [];
33020
- if (appCmd) {
33021
- try {
33022
- const synthesizer = new Synthesizer();
33023
- const context = parseContextOptions(options.context);
33024
- const result = await synthesizer.synthesize({
33025
- app: appCmd,
33026
- output: options.output || "cdk.out",
33027
- ...Object.keys(context).length > 0 && { context }
33028
- });
33029
- appStacks = result.stacks.map((s) => ({
33030
- stackName: s.stackName,
33031
- displayName: s.displayName,
33032
- ...s.region && { region: s.region }
33033
- }));
33034
- } catch {
33035
- logger.debug("Could not synthesize app, falling back to state-based stack list");
33036
- }
33037
- }
33038
- const allStateRefs = await stateBackend.listStacks();
33039
- let candidateStacks;
33040
- if (appStacks.length > 0) {
33041
- const stateNames = new Set(allStateRefs.map((r) => r.stackName));
33042
- candidateStacks = appStacks.filter((s) => stateNames.has(s.stackName));
33043
- } else if (stackArgs.length > 0 || options.stack || options.all) {
33044
- const seen = /* @__PURE__ */ new Set();
33045
- candidateStacks = [];
33046
- for (const ref of allStateRefs) {
33047
- if (seen.has(ref.stackName))
33048
- continue;
33049
- seen.add(ref.stackName);
33050
- candidateStacks.push({ stackName: ref.stackName });
33051
- }
33052
- } else {
33053
- throw new Error(
33054
- "Could not determine which stacks belong to this app. Specify stack names explicitly, use --all, or ensure --app / cdk.json is configured."
33055
- );
33056
- }
33057
- const stackPatterns = stackArgs.length > 0 ? stackArgs : options.stack ? [options.stack] : [];
33058
- let stackNames;
33059
- if (options.all) {
33060
- stackNames = candidateStacks.map((s) => s.stackName);
33061
- } else if (stackPatterns.length > 0) {
33062
- stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
33063
- } else if (candidateStacks.length === 1) {
33064
- stackNames = candidateStacks.map((s) => s.stackName);
33065
- } else if (candidateStacks.length === 0) {
33066
- logger.info("No stacks found in state");
33067
- return;
33068
- } else {
33763
+ if (!appCmd) {
33069
33764
  throw new Error(
33070
- `Multiple stacks found: ${candidateStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`
33765
+ "'cdkd orphan' requires a CDK app: pass --app or set it in cdk.json. The template is read to resolve construct paths to logical IDs."
33071
33766
  );
33072
33767
  }
33073
- if (stackNames.length === 0) {
33074
- logger.info("No matching stacks found in state");
33075
- return;
33076
- }
33077
- logger.info(`Found ${stackNames.length} stack(s) to orphan: ${stackNames.join(", ")}`);
33078
- const stateRefsByName = /* @__PURE__ */ new Map();
33079
- for (const ref of allStateRefs) {
33080
- const arr = stateRefsByName.get(ref.stackName) ?? [];
33081
- arr.push(ref);
33082
- stateRefsByName.set(ref.stackName, arr);
33768
+ logger.info("Synthesizing CDK app to read template...");
33769
+ const synthesizer = new Synthesizer();
33770
+ const context = parseContextOptions(options.context);
33771
+ const result = await synthesizer.synthesize({
33772
+ app: appCmd,
33773
+ output: options.output || "cdk.out",
33774
+ ...Object.keys(context).length > 0 && { context }
33775
+ });
33776
+ const resolved = resolveConstructPaths(pathArgs, result.stacks);
33777
+ const stackInfo = resolved.stack;
33778
+ const orphanLogicalIds = resolved.logicalIds;
33779
+ const targetRegion = await pickStackRegion(
33780
+ stateBackend,
33781
+ stackInfo.stackName,
33782
+ stackInfo.region,
33783
+ options.stackRegion
33784
+ );
33785
+ logger.info(
33786
+ `Target: ${stackInfo.stackName} (${targetRegion}); orphaning ${orphanLogicalIds.length} resource(s): ${orphanLogicalIds.join(", ")}`
33787
+ );
33788
+ const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
33789
+ if (!options.dryRun) {
33790
+ await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "orphan");
33083
33791
  }
33084
- const skipConfirmation = options.yes || options.force;
33085
- for (const stackName of stackNames) {
33086
- const refs = stateRefsByName.get(stackName) ?? [];
33087
- if (refs.length === 0) {
33088
- logger.info(`No state found for stack: ${stackName}, skipping`);
33089
- continue;
33792
+ try {
33793
+ const stateData = await stateBackend.getState(stackInfo.stackName, targetRegion);
33794
+ if (!stateData) {
33795
+ throw new Error(
33796
+ `No state found for stack '${stackInfo.stackName}' (${targetRegion}). Nothing to orphan. (Did the stack get deployed?)`
33797
+ );
33090
33798
  }
33091
- const targets = options.stackRegion ? refs.filter((r) => r.region === options.stackRegion) : refs;
33092
- if (targets.length === 0) {
33093
- const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
33799
+ const { state, etag, migrationPending } = stateData;
33800
+ const missing = orphanLogicalIds.filter((id) => !(id in state.resources));
33801
+ if (missing.length > 0) {
33802
+ const have = Object.keys(state.resources).join(", ");
33094
33803
  throw new Error(
33095
- `No state found for stack '${stackName}' in region '${options.stackRegion}'. Available regions: ${seen}.`
33804
+ `Resource(s) not in state for stack '${stackInfo.stackName}' (${targetRegion}): ${missing.join(", ")}.
33805
+ Available logical IDs: ${have}`
33096
33806
  );
33097
33807
  }
33098
- if (!options.force) {
33099
- for (const target of targets) {
33100
- const locked = await lockManager.isLocked(stackName, target.region);
33101
- if (locked) {
33102
- const where = target.region ?? "(legacy)";
33103
- throw new Error(
33104
- `Stack '${stackName}' (${where}) is locked. Run 'cdkd force-unlock ${stackName}${target.region ? ` --stack-region ${target.region}` : ""}' first, or pass --force to orphan anyway.`
33105
- );
33106
- }
33107
- }
33808
+ const providerRegistry = new ProviderRegistry();
33809
+ registerAllProviders(providerRegistry);
33810
+ const rewriteResult = await rewriteResourceReferences(
33811
+ state,
33812
+ orphanLogicalIds,
33813
+ providerRegistry,
33814
+ { force: options.force }
33815
+ );
33816
+ printRewriteSummary(rewriteResult.rewrites, orphanLogicalIds);
33817
+ if (rewriteResult.unresolvable.length > 0 && !options.force) {
33818
+ printUnresolvable(rewriteResult.unresolvable);
33819
+ throw new Error(
33820
+ `Orphan aborted: ${rewriteResult.unresolvable.length} reference(s) could not be resolved.
33821
+ Re-run with --force to fall back to cached attribute values from state, or fix the underlying provider/AWS issue and retry.`
33822
+ );
33108
33823
  }
33109
- if (!skipConfirmation) {
33110
- const targetList = targets.map((t) => t.region ? `${stackName} (${t.region})` : stackName).join(", ");
33111
- process.stdout.write(
33112
- `
33113
- WARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.
33114
- Use 'cdkd destroy ${stackName}' if you want to delete the actual resources.
33115
-
33116
- `
33824
+ if (rewriteResult.unresolvable.length > 0) {
33825
+ printUnresolvable(rewriteResult.unresolvable);
33826
+ logger.warn(
33827
+ `--force: continuing despite ${rewriteResult.unresolvable.length} unresolved reference(s); the original intrinsic was left in place where the cache also lacked the value.`
33117
33828
  );
33118
- const rl = readline2.createInterface({
33119
- input: process.stdin,
33120
- output: process.stdout
33121
- });
33122
- const answer = await rl.question(
33123
- `Orphan state for ${targetList} from s3://${stateBucket}/${options.statePrefix}/? (y/N): `
33829
+ }
33830
+ if (options.dryRun) {
33831
+ logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
33832
+ return;
33833
+ }
33834
+ if (!options.yes && !options.force) {
33835
+ const ok = await confirmPrompt(
33836
+ `Orphan ${orphanLogicalIds.length} resource(s) from cdkd state for ${stackInfo.stackName} (${targetRegion})? AWS resources will NOT be deleted.`
33124
33837
  );
33125
- rl.close();
33126
- const trimmed = answer.trim().toLowerCase();
33127
- if (trimmed !== "y" && trimmed !== "yes") {
33128
- logger.info(`Cancelled orphan of stack: ${stackName}`);
33129
- continue;
33838
+ if (!ok) {
33839
+ logger.info("Orphan cancelled.");
33840
+ return;
33130
33841
  }
33131
33842
  }
33132
- for (const target of targets) {
33133
- if (target.region) {
33134
- await stateBackend.deleteState(stackName, target.region);
33135
- await lockManager.forceReleaseLock(stackName, target.region);
33136
- } else {
33137
- await lockManager.forceReleaseLock(stackName, void 0);
33138
- }
33139
- const label = target.region ? `${stackName} (${target.region})` : stackName;
33140
- logger.info(`\u2713 Orphaned state for stack: ${label}`);
33843
+ await stateBackend.saveState(stackInfo.stackName, targetRegion, rewriteResult.state, {
33844
+ expectedEtag: etag,
33845
+ ...migrationPending && { migrateLegacy: true }
33846
+ });
33847
+ logger.info(
33848
+ `Orphaned ${orphanLogicalIds.length} resource(s) from state: ${stackInfo.stackName} (${targetRegion}). AWS resources are still in AWS; cdkd will no longer manage them.`
33849
+ );
33850
+ } finally {
33851
+ if (!options.dryRun) {
33852
+ await lockManager.releaseLock(stackInfo.stackName, targetRegion).catch((err) => {
33853
+ logger.warn(
33854
+ `Failed to release lock: ${err instanceof Error ? err.message : String(err)}`
33855
+ );
33856
+ });
33141
33857
  }
33142
33858
  }
33143
33859
  } finally {
33144
33860
  awsClients.destroy();
33145
33861
  }
33146
33862
  }
33863
+ function resolveConstructPaths(paths, stacks) {
33864
+ const byStackName = /* @__PURE__ */ new Map();
33865
+ const byDisplayName = /* @__PURE__ */ new Map();
33866
+ for (const s of stacks) {
33867
+ byStackName.set(s.stackName, s);
33868
+ byDisplayName.set(s.displayName, s);
33869
+ }
33870
+ let stack;
33871
+ const logicalIds = [];
33872
+ for (const p of paths) {
33873
+ const slash = p.indexOf("/");
33874
+ if (slash <= 0 || slash === p.length - 1) {
33875
+ throw new Error(`Invalid construct path '${p}'. Expected '<StackName>/<Path/To/Resource>'.`);
33876
+ }
33877
+ const head = p.slice(0, slash);
33878
+ const candidate = byDisplayName.get(head) ?? byStackName.get(head);
33879
+ if (!candidate) {
33880
+ const available = stacks.map((s) => s.displayName ?? s.stackName).join(", ");
33881
+ throw new Error(
33882
+ `Construct path '${p}': stack '${head}' not found in synthesized app. Available: ${available}`
33883
+ );
33884
+ }
33885
+ if (stack === void 0) {
33886
+ stack = candidate;
33887
+ } else if (stack.stackName !== candidate.stackName) {
33888
+ throw new Error(
33889
+ `All construct paths must reference the same stack. Got '${stack.stackName}' and '${candidate.stackName}'. Run 'cdkd orphan' once per stack.`
33890
+ );
33891
+ }
33892
+ const cdkPath = p;
33893
+ const index = buildCdkPathIndex(candidate.template);
33894
+ const logicalId = index.get(cdkPath);
33895
+ if (!logicalId) {
33896
+ const available = [...index.keys()].sort().join("\n ");
33897
+ throw new Error(
33898
+ `Construct path '${cdkPath}' not found in template for stack '${candidate.stackName}'.
33899
+ Available paths:
33900
+ ${available}`
33901
+ );
33902
+ }
33903
+ if (!logicalIds.includes(logicalId)) {
33904
+ logicalIds.push(logicalId);
33905
+ }
33906
+ }
33907
+ if (!stack) {
33908
+ throw new Error("No construct paths supplied.");
33909
+ }
33910
+ return { stack, logicalIds };
33911
+ }
33912
+ async function pickStackRegion(stateBackend, stackName, synthRegion, flag) {
33913
+ const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
33914
+ if (refs.length === 0) {
33915
+ if (flag)
33916
+ return flag;
33917
+ if (synthRegion)
33918
+ return synthRegion;
33919
+ throw new Error(
33920
+ `No state found for stack '${stackName}'. Run 'cdkd state list' to see available stacks.`
33921
+ );
33922
+ }
33923
+ if (flag) {
33924
+ const found = refs.find((r) => r.region === flag);
33925
+ if (!found) {
33926
+ const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
33927
+ throw new Error(
33928
+ `No state found for stack '${stackName}' in region '${flag}'. Available regions: ${seen}.`
33929
+ );
33930
+ }
33931
+ return flag;
33932
+ }
33933
+ if (synthRegion) {
33934
+ const found = refs.find((r) => r.region === synthRegion);
33935
+ if (found)
33936
+ return synthRegion;
33937
+ }
33938
+ if (refs.length === 1) {
33939
+ return refs[0].region ?? synthRegion ?? "";
33940
+ }
33941
+ const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
33942
+ throw new Error(
33943
+ `Stack '${stackName}' has state in multiple regions: ${regions}. Re-run with --stack-region <region> to disambiguate.`
33944
+ );
33945
+ }
33946
+ function printRewriteSummary(rewrites, orphanLogicalIds) {
33947
+ const logger = getLogger();
33948
+ logger.info("");
33949
+ logger.info(`Orphaning ${orphanLogicalIds.length} resource(s): ${orphanLogicalIds.join(", ")}`);
33950
+ if (rewrites.length === 0) {
33951
+ logger.info(" No sibling references \u2014 every reference was already to a non-orphan resource.");
33952
+ return;
33953
+ }
33954
+ logger.info(`Applied ${rewrites.length} rewrite(s):`);
33955
+ for (const r of rewrites) {
33956
+ const before = stringifyForAudit(r.before);
33957
+ const after = r.kind === "dependency" ? "(dropped)" : stringifyForAudit(r.after);
33958
+ logger.info(` [${r.kind}] ${r.logicalId}.${r.path}: ${before} \u2192 ${after}`);
33959
+ }
33960
+ }
33961
+ function printUnresolvable(unresolvable) {
33962
+ const logger = getLogger();
33963
+ logger.error(`${unresolvable.length} reference(s) could not be resolved:`);
33964
+ for (const u of unresolvable) {
33965
+ logger.error(` ${u.logicalId}.${u.path}: ${u.orphanLogicalId}.${u.attribute} \u2014 ${u.reason}`);
33966
+ }
33967
+ }
33968
+ function stringifyForAudit(value) {
33969
+ if (typeof value === "string")
33970
+ return JSON.stringify(value);
33971
+ return JSON.stringify(value);
33972
+ }
33973
+ async function confirmPrompt(prompt) {
33974
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
33975
+ try {
33976
+ const ans = await rl.question(`${prompt} [y/N] `);
33977
+ return /^y(es)?$/i.test(ans.trim());
33978
+ } finally {
33979
+ rl.close();
33980
+ }
33981
+ }
33147
33982
  function createOrphanCommand() {
33148
33983
  const cmd = new Command7("orphan").description(
33149
- "Remove cdkd's state record for one or more stacks (does NOT delete AWS resources). Synth-driven; for the CDK-app-free version use 'cdkd state orphan'."
33984
+ "Remove one or more resources from cdkd state by construct path (does NOT delete AWS resources). Mirrors aws-cdk-cli's 'cdk orphan --unstable=orphan'. Synth-driven; for the previous whole-stack-orphan behavior, use 'cdkd state orphan <stack>'."
33150
33985
  ).argument(
33151
- "[stacks...]",
33152
- "Stack name(s) to orphan. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*')."
33153
- ).option("--all", "Orphan all stacks in the current app", false).option(
33986
+ "<paths...>",
33987
+ "Construct paths to orphan, e.g. 'MyStack/MyTable'. Multiple paths must reference the same stack."
33988
+ ).option(
33154
33989
  "--stack-region <region>",
33155
33990
  "Region of the stack record to operate on. Required when the same stack name has state in multiple regions."
33991
+ ).option(
33992
+ "--dry-run",
33993
+ "Compute and print the rewrite audit table without acquiring a lock or saving state.",
33994
+ false
33156
33995
  ).action(withErrorHandling(orphanCommand));
33157
33996
  [
33158
33997
  ...commonOptions,
33159
33998
  ...appOptions,
33160
33999
  ...stateOptions,
33161
- ...stackOptions,
33162
34000
  ...destroyOptions,
34001
+ // adds -f / --force (escape hatch for unresolvable references + skip confirm)
33163
34002
  ...contextOptions
33164
34003
  ].forEach((opt) => cmd.addOption(opt));
33165
34004
  cmd.addOption(deprecatedRegionOption);
@@ -33351,7 +34190,7 @@ async function stateMigrateCommand(options) {
33351
34190
  }
33352
34191
  if (!options.yes) {
33353
34192
  const action = options.removeLegacy ? "and DELETE the source bucket" : "(source bucket will be kept)";
33354
- const ok = await confirmPrompt(
34193
+ const ok = await confirmPrompt2(
33355
34194
  `Copy ${sourceObjects.length} object(s) from ${legacyBucket} -> ${newBucket} ${action}?`
33356
34195
  );
33357
34196
  if (!ok) {
@@ -33556,7 +34395,7 @@ async function emptyBucketAllVersions(s3, bucket) {
33556
34395
  versionIdMarker = resp.NextVersionIdMarker;
33557
34396
  } while (keyMarker || versionIdMarker);
33558
34397
  }
33559
- async function confirmPrompt(prompt) {
34398
+ async function confirmPrompt2(prompt) {
33560
34399
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
33561
34400
  try {
33562
34401
  const ans = await rl.question(`${prompt} [y/N] `);
@@ -34013,8 +34852,8 @@ async function stateDestroyCommand(stackArgs, options) {
34013
34852
  process.env["CDKD_NO_LIVE"] = "1";
34014
34853
  }
34015
34854
  validateResourceTimeouts({
34016
- resourceWarnAfter: options.resourceWarnAfter,
34017
- resourceTimeout: options.resourceTimeout
34855
+ ...options.resourceWarnAfter && { resourceWarnAfter: options.resourceWarnAfter },
34856
+ ...options.resourceTimeout && { resourceTimeout: options.resourceTimeout }
34018
34857
  });
34019
34858
  if (!options.all && stackArgs.length === 0) {
34020
34859
  throw new Error(
@@ -34116,8 +34955,18 @@ Preparing to destroy stack: ${stackName}${ref.region ? ` (${ref.region})` : ""}`
34116
34955
  // skipped when `options.yes` is set OR `--all` was set (the user
34117
34956
  // already accepted the batch prompt).
34118
34957
  skipConfirmation: options.yes || options.all === true,
34119
- resourceWarnAfterMs: options.resourceWarnAfter,
34120
- resourceTimeoutMs: options.resourceTimeout
34958
+ ...options.resourceWarnAfter?.globalMs !== void 0 && {
34959
+ resourceWarnAfterMs: options.resourceWarnAfter.globalMs
34960
+ },
34961
+ ...options.resourceTimeout?.globalMs !== void 0 && {
34962
+ resourceTimeoutMs: options.resourceTimeout.globalMs
34963
+ },
34964
+ ...options.resourceWarnAfter?.perTypeMs && {
34965
+ resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs
34966
+ },
34967
+ ...options.resourceTimeout?.perTypeMs && {
34968
+ resourceTimeoutByType: options.resourceTimeout.perTypeMs
34969
+ }
34121
34970
  });
34122
34971
  totalErrors += result.errorCount;
34123
34972
  }
@@ -34285,7 +35134,7 @@ function createStateCommand() {
34285
35134
  }
34286
35135
 
34287
35136
  // src/cli/commands/import.ts
34288
- import { readFileSync as readFileSync5 } from "node:fs";
35137
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
34289
35138
  import * as readline5 from "node:readline/promises";
34290
35139
  import { Command as Command12 } from "commander";
34291
35140
  init_aws_clients();
@@ -34404,6 +35253,9 @@ async function importCommand(stackArg, options) {
34404
35253
  rows.push(outcome);
34405
35254
  }
34406
35255
  printSummary(rows);
35256
+ if (options.recordResourceMapping) {
35257
+ writeRecordedMapping(options.recordResourceMapping, rows);
35258
+ }
34407
35259
  if (options.dryRun) {
34408
35260
  logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
34409
35261
  return;
@@ -34414,7 +35266,7 @@ async function importCommand(stackArg, options) {
34414
35266
  return;
34415
35267
  }
34416
35268
  if (!options.yes) {
34417
- const ok = await confirmPrompt2(
35269
+ const ok = await confirmPrompt3(
34418
35270
  `Write state for ${stackInfo.stackName} (${targetRegion}) with ${importedRows.length} resource(s)?`
34419
35271
  );
34420
35272
  if (!ok) {
@@ -34557,12 +35409,24 @@ function parseMappingJson(raw, source) {
34557
35409
  }
34558
35410
  return out;
34559
35411
  }
34560
- function readCdkPath(resource) {
34561
- const meta = resource.Metadata;
34562
- if (!meta)
34563
- return "";
34564
- const v = meta["aws:cdk:path"];
34565
- return typeof v === "string" ? v : "";
35412
+ function writeRecordedMapping(filePath, rows) {
35413
+ const logger = getLogger();
35414
+ const map = {};
35415
+ for (const row of rows) {
35416
+ if (row.outcome === "imported" && row.physicalId) {
35417
+ map[row.logicalId] = row.physicalId;
35418
+ }
35419
+ }
35420
+ const body = JSON.stringify(map, null, 2) + "\n";
35421
+ try {
35422
+ writeFileSync4(filePath, body, "utf-8");
35423
+ logger.info(`Wrote resolved mapping to ${filePath} (${Object.keys(map).length} entry(ies))`);
35424
+ } catch (err) {
35425
+ const msg = err instanceof Error ? err.message : String(err);
35426
+ logger.error(
35427
+ `Failed to write --record-resource-mapping file '${filePath}': ${msg}. Continuing \u2014 the import already resolved every physical id in memory.`
35428
+ );
35429
+ }
34566
35430
  }
34567
35431
  function collectImportableResources(template) {
34568
35432
  const out = [];
@@ -34635,7 +35499,7 @@ function formatOutcome(outcome) {
34635
35499
  return "\u2717";
34636
35500
  }
34637
35501
  }
34638
- async function confirmPrompt2(prompt) {
35502
+ async function confirmPrompt3(prompt) {
34639
35503
  const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
34640
35504
  try {
34641
35505
  const ans = await rl.question(`${prompt} [y/N] `);
@@ -34661,6 +35525,9 @@ function createImportCommand() {
34661
35525
  ).option(
34662
35526
  "--resource-mapping-inline <json>",
34663
35527
  "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 \u2014 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."
35528
+ ).option(
35529
+ "--record-resource-mapping <file>",
35530
+ '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`.'
34664
35531
  ).option(
34665
35532
  "--auto",
34666
35533
  "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).",
@@ -34706,7 +35573,7 @@ function reorderArgs(argv) {
34706
35573
  }
34707
35574
  async function main() {
34708
35575
  const program = new Command13();
34709
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.25.0");
35576
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.27.0");
34710
35577
  program.addCommand(createBootstrapCommand());
34711
35578
  program.addCommand(createSynthCommand());
34712
35579
  program.addCommand(createListCommand());