@go-to-k/cdkd 0.14.0 → 0.16.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
@@ -447,7 +447,7 @@ var init_aws_clients = __esm({
447
447
  });
448
448
 
449
449
  // src/cli/index.ts
450
- import { Command as Command11 } from "commander";
450
+ import { Command as Command12 } from "commander";
451
451
 
452
452
  // src/cli/commands/bootstrap.ts
453
453
  import { Command, Option as Option2 } from "commander";
@@ -1016,7 +1016,7 @@ async function resolveStateBucketWithDefaultAndSource(cliBucket, region) {
1016
1016
  logger.warn(
1017
1017
  `Using legacy state bucket name '${legacyName}'. The default has changed to '${newName}'. To migrate, run:
1018
1018
 
1019
- cdkd state migrate-bucket --region ${region}
1019
+ cdkd state migrate --region ${region}
1020
1020
 
1021
1021
  (add --remove-legacy to delete the legacy bucket after a successful copy; legacy support will be dropped in a future release.)`
1022
1022
  );
@@ -1030,9 +1030,9 @@ async function resolveStateBucketWithDefaultAndSource(cliBucket, region) {
1030
1030
  }
1031
1031
  }
1032
1032
  async function bucketExists(client, bucketName) {
1033
- const { HeadBucketCommand: HeadBucketCommand4 } = await import("@aws-sdk/client-s3");
1033
+ const { HeadBucketCommand: HeadBucketCommand5 } = await import("@aws-sdk/client-s3");
1034
1034
  try {
1035
- await client.send(new HeadBucketCommand4({ Bucket: bucketName }));
1035
+ await client.send(new HeadBucketCommand5({ Bucket: bucketName }));
1036
1036
  return true;
1037
1037
  } catch (error) {
1038
1038
  const err = error;
@@ -7010,6 +7010,60 @@ Error: ${err.message || "Unknown error"}`,
7010
7010
  }
7011
7011
  return resourceType.startsWith("AWS::");
7012
7012
  }
7013
+ /**
7014
+ * Adopt an already-deployed resource into cdkd state via Cloud Control API.
7015
+ *
7016
+ * Strategy: explicit-override only.
7017
+ * - With `knownPhysicalId` (from `--resource <id>=<physicalId>` or
7018
+ * `--resource-mapping`): call `GetResource(TypeName, Identifier)`,
7019
+ * parse `ResourceModel` (returned as a JSON string by CC API), and
7020
+ * return its keys as `attributes`.
7021
+ * - Without `knownPhysicalId`: return `null`. CC API has no efficient
7022
+ * `aws:cdk:path`-tag lookup — `ListResources` returns identifiers
7023
+ * only, so tag lookup would require one `GetResource` per resource
7024
+ * in the account, plus per-service tag-API calls (which CC API
7025
+ * doesn't expose uniformly). Cost vs. value isn't worth it; users
7026
+ * who need adoption for CC-API-only resource types should pass
7027
+ * `--resource <id>=<physicalId>` for those resources.
7028
+ *
7029
+ * SDK providers (S3, Lambda, IAM Role, etc.) implement their own
7030
+ * `import` with tag-based auto-lookup; this fallback only kicks in for
7031
+ * resource types that don't have a dedicated SDK provider.
7032
+ */
7033
+ async import(input) {
7034
+ if (!input.knownPhysicalId) {
7035
+ return null;
7036
+ }
7037
+ try {
7038
+ const resp = await this.cloudControlClient.send(
7039
+ new GetResourceCommand2({
7040
+ TypeName: input.resourceType,
7041
+ Identifier: input.knownPhysicalId
7042
+ })
7043
+ );
7044
+ let attributes = {};
7045
+ const raw = resp.ResourceDescription?.Properties;
7046
+ if (typeof raw === "string" && raw.length > 0) {
7047
+ try {
7048
+ const parsed = JSON.parse(raw);
7049
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
7050
+ attributes = parsed;
7051
+ }
7052
+ } catch (parseErr) {
7053
+ this.logger.debug(
7054
+ `Failed to parse CC API ResourceModel for ${input.resourceType}/${input.knownPhysicalId}: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`
7055
+ );
7056
+ }
7057
+ }
7058
+ return { physicalId: input.knownPhysicalId, attributes };
7059
+ } catch (error) {
7060
+ const err = error;
7061
+ if (err.name === "ResourceNotFoundException") {
7062
+ return null;
7063
+ }
7064
+ throw error;
7065
+ }
7066
+ }
7013
7067
  };
7014
7068
 
7015
7069
  // src/provisioning/providers/custom-resource-provider.ts
@@ -7674,6 +7728,8 @@ import {
7674
7728
  UntagRoleCommand,
7675
7729
  PutRolePermissionsBoundaryCommand,
7676
7730
  DeleteRolePermissionsBoundaryCommand,
7731
+ ListRolesCommand,
7732
+ ListRoleTagsCommand,
7677
7733
  NoSuchEntityException
7678
7734
  } from "@aws-sdk/client-iam";
7679
7735
  init_aws_clients();
@@ -7782,6 +7838,32 @@ function applyDefaultNameForFallback(logicalId, resourceType, properties) {
7782
7838
  };
7783
7839
  }
7784
7840
 
7841
+ // src/provisioning/import-helpers.ts
7842
+ function readNameProperty(input, propertyName) {
7843
+ const value = input.properties?.[propertyName];
7844
+ return typeof value === "string" && value.length > 0 ? value : void 0;
7845
+ }
7846
+ function resolveExplicitPhysicalId(input, nameProperty) {
7847
+ if (input.knownPhysicalId)
7848
+ return input.knownPhysicalId;
7849
+ if (nameProperty) {
7850
+ const name = readNameProperty(input, nameProperty);
7851
+ if (name)
7852
+ return name;
7853
+ }
7854
+ return void 0;
7855
+ }
7856
+ var CDK_PATH_TAG = "aws:cdk:path";
7857
+ function matchesCdkPath(tags, cdkPath) {
7858
+ if (!tags || !cdkPath)
7859
+ return false;
7860
+ for (const t of tags) {
7861
+ if (t.Key === CDK_PATH_TAG && t.Value === cdkPath)
7862
+ return true;
7863
+ }
7864
+ return false;
7865
+ }
7866
+
7785
7867
  // src/provisioning/providers/iam-role-provider.ts
7786
7868
  var IAMRoleProvider = class {
7787
7869
  iamClient;
@@ -8288,6 +8370,58 @@ var IAMRoleProvider = class {
8288
8370
  this.logger.debug(`Added/updated ${tagsToAdd.length} tags on role ${roleName}`);
8289
8371
  }
8290
8372
  }
8373
+ /**
8374
+ * Adopt an existing IAM role into cdkd state.
8375
+ *
8376
+ * Lookup order:
8377
+ * 1. `--resource` override or `Properties.RoleName` → use directly,
8378
+ * verify via `GetRole`.
8379
+ * 2. `ListRoles` + `ListRoleTags`, match `aws:cdk:path` tag.
8380
+ *
8381
+ * `ListRoles` is paginated and IAM is global (no region scoping), so this
8382
+ * walks every role in the account once. Acceptable for the cardinalities
8383
+ * we expect (typically <100 roles per account); larger accounts may want
8384
+ * to provide `--resource` overrides instead.
8385
+ */
8386
+ async import(input) {
8387
+ const explicit = resolveExplicitPhysicalId(input, "RoleName");
8388
+ if (explicit) {
8389
+ try {
8390
+ await this.iamClient.send(new GetRoleCommand({ RoleName: explicit }));
8391
+ return { physicalId: explicit, attributes: {} };
8392
+ } catch (err) {
8393
+ if (err instanceof NoSuchEntityException)
8394
+ return null;
8395
+ throw err;
8396
+ }
8397
+ }
8398
+ if (!input.cdkPath)
8399
+ return null;
8400
+ let marker;
8401
+ do {
8402
+ const list = await this.iamClient.send(
8403
+ new ListRolesCommand({ ...marker && { Marker: marker } })
8404
+ );
8405
+ for (const role of list.Roles ?? []) {
8406
+ if (!role.RoleName)
8407
+ continue;
8408
+ try {
8409
+ const tags = await this.iamClient.send(
8410
+ new ListRoleTagsCommand({ RoleName: role.RoleName })
8411
+ );
8412
+ if (matchesCdkPath(tags.Tags, input.cdkPath)) {
8413
+ return { physicalId: role.RoleName, attributes: {} };
8414
+ }
8415
+ } catch (err) {
8416
+ if (err instanceof NoSuchEntityException)
8417
+ continue;
8418
+ throw err;
8419
+ }
8420
+ }
8421
+ marker = list.IsTruncated ? list.Marker : void 0;
8422
+ } while (marker);
8423
+ return null;
8424
+ }
8291
8425
  };
8292
8426
 
8293
8427
  // src/provisioning/providers/iam-policy-provider.ts
@@ -9868,6 +10002,8 @@ var IAMUserGroupProvider = class {
9868
10002
  import {
9869
10003
  CreateBucketCommand as CreateBucketCommand2,
9870
10004
  DeleteBucketCommand,
10005
+ HeadBucketCommand as HeadBucketCommand3,
10006
+ ListBucketsCommand,
9871
10007
  PutBucketVersioningCommand as PutBucketVersioningCommand2,
9872
10008
  PutBucketTaggingCommand,
9873
10009
  PutBucketOwnershipControlsCommand,
@@ -9885,6 +10021,7 @@ import {
9885
10021
  PutBucketInventoryConfigurationCommand,
9886
10022
  PutBucketReplicationCommand,
9887
10023
  PutObjectLockConfigurationCommand,
10024
+ GetBucketTaggingCommand,
9888
10025
  NoSuchBucket,
9889
10026
  ListObjectVersionsCommand,
9890
10027
  DeleteObjectsCommand
@@ -10723,6 +10860,53 @@ var S3BucketProvider = class {
10723
10860
  );
10724
10861
  }
10725
10862
  }
10863
+ /**
10864
+ * Adopt an existing S3 bucket into cdkd state.
10865
+ *
10866
+ * Lookup order:
10867
+ * 1. `--resource <id>=<name>` override or `Properties.BucketName` → use directly,
10868
+ * verify with `HeadBucket`.
10869
+ * 2. `ListBuckets` + `GetBucketTagging`, match `aws:cdk:path` against the
10870
+ * CDK construct path.
10871
+ *
10872
+ * Returns `null` when nothing matches — caller treats this as
10873
+ * "not deployed yet" rather than a failure.
10874
+ */
10875
+ async import(input) {
10876
+ const explicit = resolveExplicitPhysicalId(input, "BucketName");
10877
+ if (explicit) {
10878
+ try {
10879
+ await this.s3Client.send(new HeadBucketCommand3({ Bucket: explicit }));
10880
+ return { physicalId: explicit, attributes: {} };
10881
+ } catch (err) {
10882
+ const e = err;
10883
+ if (e.name === "NotFound" || e.name === "NoSuchBucket") {
10884
+ return null;
10885
+ }
10886
+ throw err;
10887
+ }
10888
+ }
10889
+ if (!input.cdkPath)
10890
+ return null;
10891
+ const list = await this.s3Client.send(new ListBucketsCommand({}));
10892
+ for (const b of list.Buckets ?? []) {
10893
+ if (!b.Name)
10894
+ continue;
10895
+ try {
10896
+ const tagging = await this.s3Client.send(new GetBucketTaggingCommand({ Bucket: b.Name }));
10897
+ if (matchesCdkPath(tagging.TagSet, input.cdkPath)) {
10898
+ return { physicalId: b.Name, attributes: {} };
10899
+ }
10900
+ } catch (err) {
10901
+ const e = err;
10902
+ if (e.name === "NoSuchTagSet" || e.name === "AccessDenied" || e.$metadata?.httpStatusCode === 301) {
10903
+ continue;
10904
+ }
10905
+ throw err;
10906
+ }
10907
+ }
10908
+ return null;
10909
+ }
10726
10910
  /**
10727
10911
  * Delete a bucket, emptying it first if not empty.
10728
10912
  * Handles the race condition where objects (e.g., ALB logs) are written
@@ -10959,6 +11143,9 @@ import {
10959
11143
  CreateQueueCommand,
10960
11144
  DeleteQueueCommand,
10961
11145
  GetQueueAttributesCommand,
11146
+ GetQueueUrlCommand,
11147
+ ListQueuesCommand,
11148
+ ListQueueTagsCommand,
10962
11149
  SetQueueAttributesCommand,
10963
11150
  QueueDoesNotExist
10964
11151
  } from "@aws-sdk/client-sqs";
@@ -11167,6 +11354,68 @@ var SQSQueueProvider = class {
11167
11354
  return `arn:aws:sqs:unknown:unknown:${queueName}`;
11168
11355
  }
11169
11356
  }
11357
+ /**
11358
+ * Adopt an existing SQS queue into cdkd state.
11359
+ *
11360
+ * SQS physical IDs are queue URLs (`https://sqs.us-east-1.amazonaws.com/<account>/<name>`).
11361
+ *
11362
+ * Lookup order:
11363
+ * 1. `--resource` override (URL) → verify via `GetQueueAttributes`.
11364
+ * 2. `Properties.QueueName` → `GetQueueUrl` for direct lookup.
11365
+ * 3. `aws:cdk:path` tag match via `ListQueues` + `ListQueueTags`.
11366
+ */
11367
+ async import(input) {
11368
+ if (input.knownPhysicalId) {
11369
+ try {
11370
+ await this.sqsClient.send(
11371
+ new GetQueueAttributesCommand({
11372
+ QueueUrl: input.knownPhysicalId,
11373
+ AttributeNames: ["QueueArn"]
11374
+ })
11375
+ );
11376
+ return { physicalId: input.knownPhysicalId, attributes: {} };
11377
+ } catch (err) {
11378
+ if (err instanceof QueueDoesNotExist)
11379
+ return null;
11380
+ throw err;
11381
+ }
11382
+ }
11383
+ const explicitName = resolveExplicitPhysicalId(input, "QueueName");
11384
+ if (explicitName && !input.knownPhysicalId) {
11385
+ try {
11386
+ const resp = await this.sqsClient.send(new GetQueueUrlCommand({ QueueName: explicitName }));
11387
+ if (resp.QueueUrl)
11388
+ return { physicalId: resp.QueueUrl, attributes: {} };
11389
+ return null;
11390
+ } catch (err) {
11391
+ if (err instanceof QueueDoesNotExist)
11392
+ return null;
11393
+ throw err;
11394
+ }
11395
+ }
11396
+ if (!input.cdkPath)
11397
+ return null;
11398
+ let nextToken;
11399
+ do {
11400
+ const list = await this.sqsClient.send(
11401
+ new ListQueuesCommand({ ...nextToken && { NextToken: nextToken } })
11402
+ );
11403
+ for (const url of list.QueueUrls ?? []) {
11404
+ try {
11405
+ const tagsResp = await this.sqsClient.send(new ListQueueTagsCommand({ QueueUrl: url }));
11406
+ if (tagsResp.Tags?.[CDK_PATH_TAG] === input.cdkPath) {
11407
+ return { physicalId: url, attributes: {} };
11408
+ }
11409
+ } catch (err) {
11410
+ if (err instanceof QueueDoesNotExist)
11411
+ continue;
11412
+ throw err;
11413
+ }
11414
+ }
11415
+ nextToken = list.NextToken;
11416
+ } while (nextToken);
11417
+ return null;
11418
+ }
11170
11419
  };
11171
11420
 
11172
11421
  // src/provisioning/providers/sqs-queue-policy-provider.ts
@@ -11329,6 +11578,9 @@ var SQSQueuePolicyProvider = class {
11329
11578
  import {
11330
11579
  CreateTopicCommand,
11331
11580
  DeleteTopicCommand,
11581
+ GetTopicAttributesCommand,
11582
+ ListTopicsCommand,
11583
+ ListTagsForResourceCommand,
11332
11584
  SetTopicAttributesCommand,
11333
11585
  TagResourceCommand,
11334
11586
  UntagResourceCommand,
@@ -11618,6 +11870,63 @@ var SNSTopicProvider = class {
11618
11870
  );
11619
11871
  }
11620
11872
  }
11873
+ /**
11874
+ * Adopt an existing SNS topic into cdkd state.
11875
+ *
11876
+ * SNS physical IDs are full ARNs (`arn:aws:sns:...:TopicName`). The
11877
+ * `--resource` override is expected to receive an ARN; bare topic names
11878
+ * trigger a `ListTopics` walk that resolves to the ARN.
11879
+ *
11880
+ * Lookup order:
11881
+ * 1. `--resource` override → trust as ARN, verify via `GetTopicAttributes`.
11882
+ * 2. `Properties.TopicName` → `ListTopics` to find matching ARN.
11883
+ * 3. `aws:cdk:path` tag match via `ListTopics` + `ListTagsForResource`.
11884
+ */
11885
+ async import(input) {
11886
+ if (input.knownPhysicalId) {
11887
+ try {
11888
+ await this.snsClient.send(
11889
+ new GetTopicAttributesCommand({ TopicArn: input.knownPhysicalId })
11890
+ );
11891
+ return { physicalId: input.knownPhysicalId, attributes: {} };
11892
+ } catch (err) {
11893
+ if (err instanceof NotFoundException)
11894
+ return null;
11895
+ throw err;
11896
+ }
11897
+ }
11898
+ const desiredName = typeof input.properties?.["TopicName"] === "string" ? input.properties["TopicName"] : void 0;
11899
+ let nextToken;
11900
+ do {
11901
+ const list = await this.snsClient.send(
11902
+ new ListTopicsCommand({ ...nextToken && { NextToken: nextToken } })
11903
+ );
11904
+ for (const t of list.Topics ?? []) {
11905
+ if (!t.TopicArn)
11906
+ continue;
11907
+ const arnTail = t.TopicArn.substring(t.TopicArn.lastIndexOf(":") + 1);
11908
+ if (desiredName && arnTail === desiredName) {
11909
+ return { physicalId: t.TopicArn, attributes: {} };
11910
+ }
11911
+ if (input.cdkPath) {
11912
+ try {
11913
+ const tagsResp = await this.snsClient.send(
11914
+ new ListTagsForResourceCommand({ ResourceArn: t.TopicArn })
11915
+ );
11916
+ if (matchesCdkPath(tagsResp.Tags, input.cdkPath)) {
11917
+ return { physicalId: t.TopicArn, attributes: {} };
11918
+ }
11919
+ } catch (err) {
11920
+ if (err instanceof NotFoundException)
11921
+ continue;
11922
+ throw err;
11923
+ }
11924
+ }
11925
+ }
11926
+ nextToken = list.NextToken;
11927
+ } while (nextToken);
11928
+ return null;
11929
+ }
11621
11930
  };
11622
11931
 
11623
11932
  // src/provisioning/providers/sns-subscription-provider.ts
@@ -11917,6 +12226,8 @@ import {
11917
12226
  UpdateFunctionCodeCommand,
11918
12227
  DeleteFunctionCommand,
11919
12228
  GetFunctionCommand,
12229
+ ListFunctionsCommand,
12230
+ ListTagsCommand,
11920
12231
  ResourceNotFoundException
11921
12232
  } from "@aws-sdk/client-lambda";
11922
12233
  import {
@@ -12501,6 +12812,57 @@ var LambdaFunctionProvider = class {
12501
12812
  }
12502
12813
  return (crc ^ 4294967295) >>> 0;
12503
12814
  }
12815
+ /**
12816
+ * Adopt an existing Lambda function into cdkd state.
12817
+ *
12818
+ * Lookup order:
12819
+ * 1. `--resource` override or `Properties.FunctionName` → use directly,
12820
+ * verify via `GetFunction`.
12821
+ * 2. `ListFunctions` + `ListTags`, match `aws:cdk:path` tag.
12822
+ *
12823
+ * Lambda's `ListTags` returns a `Tags` map keyed by tag name (unlike
12824
+ * EC2/S3 which return an array of `{Key, Value}`), so we read it directly
12825
+ * instead of going through the shared `matchesCdkPath` helper.
12826
+ */
12827
+ async import(input) {
12828
+ const explicit = resolveExplicitPhysicalId(input, "FunctionName");
12829
+ if (explicit) {
12830
+ try {
12831
+ await this.lambdaClient.send(new GetFunctionCommand({ FunctionName: explicit }));
12832
+ return { physicalId: explicit, attributes: {} };
12833
+ } catch (err) {
12834
+ if (err instanceof ResourceNotFoundException)
12835
+ return null;
12836
+ throw err;
12837
+ }
12838
+ }
12839
+ if (!input.cdkPath)
12840
+ return null;
12841
+ let marker;
12842
+ do {
12843
+ const list = await this.lambdaClient.send(
12844
+ new ListFunctionsCommand({ ...marker && { Marker: marker } })
12845
+ );
12846
+ for (const fn of list.Functions ?? []) {
12847
+ if (!fn.FunctionArn || !fn.FunctionName)
12848
+ continue;
12849
+ try {
12850
+ const tagsResp = await this.lambdaClient.send(
12851
+ new ListTagsCommand({ Resource: fn.FunctionArn })
12852
+ );
12853
+ if (tagsResp.Tags?.[CDK_PATH_TAG] === input.cdkPath) {
12854
+ return { physicalId: fn.FunctionName, attributes: {} };
12855
+ }
12856
+ } catch (err) {
12857
+ if (err instanceof ResourceNotFoundException)
12858
+ continue;
12859
+ throw err;
12860
+ }
12861
+ }
12862
+ marker = list.NextMarker;
12863
+ } while (marker);
12864
+ return null;
12865
+ }
12504
12866
  };
12505
12867
 
12506
12868
  // src/provisioning/providers/lambda-permission-provider.ts
@@ -13222,6 +13584,8 @@ import {
13222
13584
  CreateTableCommand,
13223
13585
  DeleteTableCommand,
13224
13586
  DescribeTableCommand as DescribeTableCommand2,
13587
+ ListTablesCommand,
13588
+ ListTagsOfResourceCommand,
13225
13589
  ResourceNotFoundException as ResourceNotFoundException6
13226
13590
  } from "@aws-sdk/client-dynamodb";
13227
13591
  init_aws_clients();
@@ -13433,12 +13797,70 @@ var DynamoDBTableProvider = class {
13433
13797
  }
13434
13798
  throw new Error(`Table ${tableName} did not reach ACTIVE status within ${maxAttempts} seconds`);
13435
13799
  }
13800
+ /**
13801
+ * Adopt an existing DynamoDB table into cdkd state.
13802
+ *
13803
+ * Lookup order:
13804
+ * 1. `--resource` override or `Properties.TableName` → verify via `DescribeTable`.
13805
+ * 2. `ListTables` + `ListTagsOfResource`, match `aws:cdk:path` tag.
13806
+ *
13807
+ * Tags require the table ARN, which `DescribeTable` provides; the loop
13808
+ * therefore costs one `DescribeTable` per table just to read the ARN.
13809
+ * Acceptable for typical DynamoDB cardinalities.
13810
+ */
13811
+ async import(input) {
13812
+ const explicit = resolveExplicitPhysicalId(input, "TableName");
13813
+ if (explicit) {
13814
+ try {
13815
+ await this.dynamoDBClient.send(new DescribeTableCommand2({ TableName: explicit }));
13816
+ return { physicalId: explicit, attributes: {} };
13817
+ } catch (err) {
13818
+ if (err instanceof ResourceNotFoundException6)
13819
+ return null;
13820
+ throw err;
13821
+ }
13822
+ }
13823
+ if (!input.cdkPath)
13824
+ return null;
13825
+ let exclusiveStartTableName;
13826
+ do {
13827
+ const list = await this.dynamoDBClient.send(
13828
+ new ListTablesCommand({
13829
+ ...exclusiveStartTableName && { ExclusiveStartTableName: exclusiveStartTableName }
13830
+ })
13831
+ );
13832
+ for (const name of list.TableNames ?? []) {
13833
+ try {
13834
+ const desc = await this.dynamoDBClient.send(
13835
+ new DescribeTableCommand2({ TableName: name })
13836
+ );
13837
+ const arn = desc.Table?.TableArn;
13838
+ if (!arn)
13839
+ continue;
13840
+ const tagsResp = await this.dynamoDBClient.send(
13841
+ new ListTagsOfResourceCommand({ ResourceArn: arn })
13842
+ );
13843
+ if (matchesCdkPath(tagsResp.Tags, input.cdkPath)) {
13844
+ return { physicalId: name, attributes: {} };
13845
+ }
13846
+ } catch (err) {
13847
+ if (err instanceof ResourceNotFoundException6)
13848
+ continue;
13849
+ throw err;
13850
+ }
13851
+ }
13852
+ exclusiveStartTableName = list.LastEvaluatedTableName;
13853
+ } while (exclusiveStartTableName);
13854
+ return null;
13855
+ }
13436
13856
  };
13437
13857
 
13438
13858
  // src/provisioning/providers/logs-loggroup-provider.ts
13439
13859
  import {
13440
13860
  CreateLogGroupCommand,
13441
13861
  DeleteLogGroupCommand,
13862
+ DescribeLogGroupsCommand,
13863
+ ListTagsForResourceCommand as ListTagsForResourceCommand2,
13442
13864
  PutRetentionPolicyCommand,
13443
13865
  DeleteRetentionPolicyCommand,
13444
13866
  TagResourceCommand as TagResourceCommand2,
@@ -13662,6 +14084,61 @@ var LogsLogGroupProvider = class {
13662
14084
  return `arn:aws:logs:unknown:unknown:log-group:${logGroupName}:*`;
13663
14085
  }
13664
14086
  }
14087
+ /**
14088
+ * Adopt an existing CloudWatch Logs log group into cdkd state.
14089
+ *
14090
+ * Lookup order:
14091
+ * 1. `--resource` override or `Properties.LogGroupName` → verify via
14092
+ * `DescribeLogGroups` (filtered by name prefix).
14093
+ * 2. `aws:cdk:path` tag match via `DescribeLogGroups` + `ListTagsForResource`.
14094
+ *
14095
+ * `ListTagsForResource` for log groups uses the log-group ARN. The
14096
+ * `DescribeLogGroups` response includes the ARN, so no extra round-trip
14097
+ * is needed beyond the per-group tag lookup.
14098
+ */
14099
+ async import(input) {
14100
+ const explicit = resolveExplicitPhysicalId(input, "LogGroupName");
14101
+ if (explicit) {
14102
+ try {
14103
+ const resp = await this.logsClient.send(
14104
+ new DescribeLogGroupsCommand({ logGroupNamePrefix: explicit })
14105
+ );
14106
+ const found = resp.logGroups?.find((g) => g.logGroupName === explicit);
14107
+ return found ? { physicalId: explicit, attributes: {} } : null;
14108
+ } catch (err) {
14109
+ if (err instanceof ResourceNotFoundException7)
14110
+ return null;
14111
+ throw err;
14112
+ }
14113
+ }
14114
+ if (!input.cdkPath)
14115
+ return null;
14116
+ let nextToken;
14117
+ do {
14118
+ const list = await this.logsClient.send(
14119
+ new DescribeLogGroupsCommand({ ...nextToken && { nextToken } })
14120
+ );
14121
+ for (const g of list.logGroups ?? []) {
14122
+ if (!g.logGroupName || !g.arn)
14123
+ continue;
14124
+ const arnForTags = g.arn.replace(/:\*$/, "");
14125
+ try {
14126
+ const tagsResp = await this.logsClient.send(
14127
+ new ListTagsForResourceCommand2({ resourceArn: arnForTags })
14128
+ );
14129
+ if (tagsResp.tags?.["aws:cdk:path"] === input.cdkPath) {
14130
+ return { physicalId: g.logGroupName, attributes: {} };
14131
+ }
14132
+ } catch (err) {
14133
+ if (err instanceof ResourceNotFoundException7)
14134
+ continue;
14135
+ throw err;
14136
+ }
14137
+ }
14138
+ nextToken = list.nextToken;
14139
+ } while (nextToken);
14140
+ return null;
14141
+ }
13665
14142
  };
13666
14143
 
13667
14144
  // src/provisioning/providers/cloudwatch-alarm-provider.ts
@@ -13892,6 +14369,8 @@ var CloudWatchAlarmProvider = class {
13892
14369
  import {
13893
14370
  CreateSecretCommand,
13894
14371
  DeleteSecretCommand,
14372
+ DescribeSecretCommand,
14373
+ ListSecretsCommand,
13895
14374
  UpdateSecretCommand,
13896
14375
  TagResourceCommand as TagResourceCommand3,
13897
14376
  UntagResourceCommand as UntagResourceCommand3,
@@ -14159,10 +14638,65 @@ var SecretsManagerSecretProvider = class {
14159
14638
  }
14160
14639
  return password;
14161
14640
  }
14641
+ /**
14642
+ * Adopt an existing Secrets Manager secret into cdkd state.
14643
+ *
14644
+ * Secrets Manager physical IDs are full secret ARNs. The CDK template's
14645
+ * `Properties.Name` (secret name) is enough to fetch the ARN via
14646
+ * `DescribeSecret`.
14647
+ *
14648
+ * Lookup order:
14649
+ * 1. `--resource` override (ARN) → verify via `DescribeSecret`.
14650
+ * 2. `Properties.Name` → `DescribeSecret` (accepts name).
14651
+ * 3. `aws:cdk:path` tag match via `ListSecrets` (which already returns Tags).
14652
+ */
14653
+ async import(input) {
14654
+ if (input.knownPhysicalId) {
14655
+ try {
14656
+ const resp = await this.smClient.send(
14657
+ new DescribeSecretCommand({ SecretId: input.knownPhysicalId })
14658
+ );
14659
+ return resp.ARN ? { physicalId: resp.ARN, attributes: {} } : null;
14660
+ } catch (err) {
14661
+ if (err instanceof ResourceNotFoundException8)
14662
+ return null;
14663
+ throw err;
14664
+ }
14665
+ }
14666
+ const name = typeof input.properties?.["Name"] === "string" ? input.properties["Name"] : void 0;
14667
+ if (name) {
14668
+ try {
14669
+ const resp = await this.smClient.send(new DescribeSecretCommand({ SecretId: name }));
14670
+ return resp.ARN ? { physicalId: resp.ARN, attributes: {} } : null;
14671
+ } catch (err) {
14672
+ if (err instanceof ResourceNotFoundException8)
14673
+ return null;
14674
+ throw err;
14675
+ }
14676
+ }
14677
+ if (!input.cdkPath)
14678
+ return null;
14679
+ let nextToken;
14680
+ do {
14681
+ const list = await this.smClient.send(
14682
+ new ListSecretsCommand({ ...nextToken && { NextToken: nextToken } })
14683
+ );
14684
+ for (const s of list.SecretList ?? []) {
14685
+ if (s.ARN && matchesCdkPath(s.Tags, input.cdkPath)) {
14686
+ return { physicalId: s.ARN, attributes: {} };
14687
+ }
14688
+ }
14689
+ nextToken = list.NextToken;
14690
+ } while (nextToken);
14691
+ return null;
14692
+ }
14162
14693
  };
14163
14694
 
14164
14695
  // src/provisioning/providers/ssm-parameter-provider.ts
14165
14696
  import {
14697
+ DescribeParametersCommand,
14698
+ GetParameterCommand as GetParameterCommand3,
14699
+ ListTagsForResourceCommand as ListTagsForResourceCommand3,
14166
14700
  PutParameterCommand,
14167
14701
  DeleteParameterCommand,
14168
14702
  AddTagsToResourceCommand,
@@ -14374,6 +14908,57 @@ var SSMParameterProvider = class {
14374
14908
  );
14375
14909
  }
14376
14910
  }
14911
+ /**
14912
+ * Adopt an existing SSM parameter into cdkd state.
14913
+ *
14914
+ * SSM physical IDs ARE the parameter names (`/foo/bar`). The CDK template
14915
+ * usually carries `Properties.Name` explicitly, so the explicit-name path
14916
+ * covers most cases. The tag-based fallback is rarely needed.
14917
+ *
14918
+ * Lookup order:
14919
+ * 1. `--resource` override or `Properties.Name` → verify via `GetParameter`.
14920
+ * 2. `aws:cdk:path` tag match via `DescribeParameters` + `ListTagsForResource`
14921
+ * (`ResourceType: 'Parameter'`, `ResourceId: <name>`).
14922
+ */
14923
+ async import(input) {
14924
+ const explicit = resolveExplicitPhysicalId(input, "Name");
14925
+ if (explicit) {
14926
+ try {
14927
+ await this.ssmClient.send(new GetParameterCommand3({ Name: explicit }));
14928
+ return { physicalId: explicit, attributes: {} };
14929
+ } catch (err) {
14930
+ if (err instanceof ParameterNotFound)
14931
+ return null;
14932
+ throw err;
14933
+ }
14934
+ }
14935
+ if (!input.cdkPath)
14936
+ return null;
14937
+ let nextToken;
14938
+ do {
14939
+ const list = await this.ssmClient.send(
14940
+ new DescribeParametersCommand({ ...nextToken && { NextToken: nextToken } })
14941
+ );
14942
+ for (const p of list.Parameters ?? []) {
14943
+ if (!p.Name)
14944
+ continue;
14945
+ try {
14946
+ const tagsResp = await this.ssmClient.send(
14947
+ new ListTagsForResourceCommand3({ ResourceType: "Parameter", ResourceId: p.Name })
14948
+ );
14949
+ if (matchesCdkPath(tagsResp.TagList, input.cdkPath)) {
14950
+ return { physicalId: p.Name, attributes: {} };
14951
+ }
14952
+ } catch (err) {
14953
+ if (err instanceof ParameterNotFound)
14954
+ continue;
14955
+ throw err;
14956
+ }
14957
+ }
14958
+ nextToken = list.NextToken;
14959
+ } while (nextToken);
14960
+ return null;
14961
+ }
14377
14962
  };
14378
14963
 
14379
14964
  // src/provisioning/providers/eventbridge-rule-provider.ts
@@ -14672,7 +15257,9 @@ import {
14672
15257
  DeleteEventBusCommand,
14673
15258
  UpdateEventBusCommand,
14674
15259
  DescribeEventBusCommand,
15260
+ ListEventBusesCommand,
14675
15261
  ListRulesCommand,
15262
+ ListTagsForResourceCommand as ListTagsForResourceCommand4,
14676
15263
  RemoveTargetsCommand as RemoveTargetsCommand2,
14677
15264
  DeleteRuleCommand as DeleteRuleCommand2,
14678
15265
  ListTargetsByRuleCommand as ListTargetsByRuleCommand2,
@@ -14913,6 +15500,52 @@ var EventBridgeBusProvider = class {
14913
15500
  );
14914
15501
  }
14915
15502
  }
15503
+ /**
15504
+ * Adopt an existing EventBridge event bus into cdkd state.
15505
+ *
15506
+ * Lookup order:
15507
+ * 1. `--resource` override or `Properties.Name` → verify via `DescribeEventBus`.
15508
+ * 2. `aws:cdk:path` tag match via `ListEventBuses` + `ListTagsForResource`.
15509
+ */
15510
+ async import(input) {
15511
+ const explicit = resolveExplicitPhysicalId(input, "Name");
15512
+ if (explicit) {
15513
+ try {
15514
+ await this.eventBridgeClient.send(new DescribeEventBusCommand({ Name: explicit }));
15515
+ return { physicalId: explicit, attributes: {} };
15516
+ } catch (err) {
15517
+ if (err instanceof ResourceNotFoundException10)
15518
+ return null;
15519
+ throw err;
15520
+ }
15521
+ }
15522
+ if (!input.cdkPath)
15523
+ return null;
15524
+ let nextToken;
15525
+ do {
15526
+ const list = await this.eventBridgeClient.send(
15527
+ new ListEventBusesCommand({ ...nextToken && { NextToken: nextToken } })
15528
+ );
15529
+ for (const bus of list.EventBuses ?? []) {
15530
+ if (!bus.Name || !bus.Arn)
15531
+ continue;
15532
+ try {
15533
+ const tagsResp = await this.eventBridgeClient.send(
15534
+ new ListTagsForResourceCommand4({ ResourceARN: bus.Arn })
15535
+ );
15536
+ if (matchesCdkPath(tagsResp.Tags, input.cdkPath)) {
15537
+ return { physicalId: bus.Name, attributes: {} };
15538
+ }
15539
+ } catch (err) {
15540
+ if (err instanceof ResourceNotFoundException10)
15541
+ continue;
15542
+ throw err;
15543
+ }
15544
+ }
15545
+ nextToken = list.NextToken;
15546
+ } while (nextToken);
15547
+ return null;
15548
+ }
14916
15549
  };
14917
15550
 
14918
15551
  // src/provisioning/providers/ec2-provider.ts
@@ -24219,6 +24852,10 @@ var GlueProvider = class {
24219
24852
  import {
24220
24853
  KMSClient as KMSClient2,
24221
24854
  CreateKeyCommand,
24855
+ DescribeKeyCommand,
24856
+ ListAliasesCommand as ListAliasesCommand2,
24857
+ ListKeysCommand,
24858
+ ListResourceTagsCommand,
24222
24859
  ScheduleKeyDeletionCommand,
24223
24860
  CreateAliasCommand,
24224
24861
  DeleteAliasCommand,
@@ -24601,6 +25238,81 @@ var KMSProvider = class {
24601
25238
  );
24602
25239
  }
24603
25240
  }
25241
+ /**
25242
+ * Adopt an existing KMS key or alias into cdkd state.
25243
+ *
25244
+ * KMS keys have no `Properties.KeyName` field — physical IDs are
25245
+ * AWS-generated UUIDs. So:
25246
+ * - For `AWS::KMS::Key`: `--resource MyKey=<keyId>` is the only explicit
25247
+ * path; auto-lookup walks `ListKeys` + `ListResourceTags` matching
25248
+ * `aws:cdk:path`.
25249
+ * - For `AWS::KMS::Alias`: `Properties.AliasName` is explicit and reliable.
25250
+ */
25251
+ async import(input) {
25252
+ if (input.resourceType === "AWS::KMS::Alias") {
25253
+ const aliasName = input.knownPhysicalId ?? (typeof input.properties?.["AliasName"] === "string" ? input.properties["AliasName"] : void 0);
25254
+ if (!aliasName)
25255
+ return null;
25256
+ try {
25257
+ let marker2;
25258
+ do {
25259
+ const list = await this.getClient().send(
25260
+ new ListAliasesCommand2({ ...marker2 && { Marker: marker2 } })
25261
+ );
25262
+ const found = list.Aliases?.find(
25263
+ (a) => a.AliasName === aliasName
25264
+ );
25265
+ if (found)
25266
+ return { physicalId: aliasName, attributes: {} };
25267
+ marker2 = list.NextMarker;
25268
+ } while (marker2);
25269
+ return null;
25270
+ } catch (err) {
25271
+ if (err instanceof NotFoundException5)
25272
+ return null;
25273
+ throw err;
25274
+ }
25275
+ }
25276
+ if (input.knownPhysicalId) {
25277
+ try {
25278
+ await this.getClient().send(new DescribeKeyCommand({ KeyId: input.knownPhysicalId }));
25279
+ return { physicalId: input.knownPhysicalId, attributes: {} };
25280
+ } catch (err) {
25281
+ if (err instanceof NotFoundException5)
25282
+ return null;
25283
+ throw err;
25284
+ }
25285
+ }
25286
+ if (!input.cdkPath)
25287
+ return null;
25288
+ let marker;
25289
+ do {
25290
+ const list = await this.getClient().send(
25291
+ new ListKeysCommand({ ...marker && { Marker: marker } })
25292
+ );
25293
+ for (const key of list.Keys ?? []) {
25294
+ if (!key.KeyId)
25295
+ continue;
25296
+ try {
25297
+ const tagsResp = await this.getClient().send(
25298
+ new ListResourceTagsCommand({ KeyId: key.KeyId })
25299
+ );
25300
+ for (const tag of tagsResp.Tags ?? []) {
25301
+ if (tag.TagKey === CDK_PATH_TAG && tag.TagValue === input.cdkPath) {
25302
+ return { physicalId: key.KeyId, attributes: {} };
25303
+ }
25304
+ }
25305
+ } catch (err) {
25306
+ const name = err.name;
25307
+ if (name === "AccessDeniedException" || err instanceof NotFoundException5)
25308
+ continue;
25309
+ throw err;
25310
+ }
25311
+ }
25312
+ marker = list.NextMarker;
25313
+ } while (marker);
25314
+ return null;
25315
+ }
24604
25316
  };
24605
25317
 
24606
25318
  // src/provisioning/providers/kinesis-provider.ts
@@ -26579,7 +27291,7 @@ import {
26579
27291
  CreateTableCommand as CreateTableCommand3,
26580
27292
  DeleteTableCommand as DeleteTableCommand3,
26581
27293
  ListNamespacesCommand,
26582
- ListTablesCommand,
27294
+ ListTablesCommand as ListTablesCommand2,
26583
27295
  NotFoundException as NotFoundException6
26584
27296
  } from "@aws-sdk/client-s3tables";
26585
27297
  var S3TablesProvider = class {
@@ -26725,7 +27437,7 @@ var S3TablesProvider = class {
26725
27437
  let tableContinuationToken;
26726
27438
  do {
26727
27439
  const tablesResult = await this.getClient().send(
26728
- new ListTablesCommand({
27440
+ new ListTablesCommand2({
26729
27441
  tableBucketARN,
26730
27442
  namespace: namespaceName,
26731
27443
  continuationToken: tableContinuationToken
@@ -29564,7 +30276,7 @@ import {
29564
30276
  } from "@aws-sdk/client-s3";
29565
30277
  init_aws_clients();
29566
30278
 
29567
- // src/cli/commands/state-migrate-bucket.ts
30279
+ // src/cli/commands/state-migrate.ts
29568
30280
  import * as readline2 from "node:readline/promises";
29569
30281
  import { Command as Command9 } from "commander";
29570
30282
  import {
@@ -29572,7 +30284,7 @@ import {
29572
30284
  CreateBucketCommand as CreateBucketCommand4,
29573
30285
  DeleteBucketCommand as DeleteBucketCommand3,
29574
30286
  DeleteObjectsCommand as DeleteObjectsCommand3,
29575
- HeadBucketCommand as HeadBucketCommand3,
30287
+ HeadBucketCommand as HeadBucketCommand4,
29576
30288
  ListObjectVersionsCommand as ListObjectVersionsCommand2,
29577
30289
  ListObjectsV2Command as ListObjectsV2Command3,
29578
30290
  PutBucketEncryptionCommand as PutBucketEncryptionCommand3,
@@ -29582,7 +30294,7 @@ import {
29582
30294
  } from "@aws-sdk/client-s3";
29583
30295
  import { GetCallerIdentityCommand as GetCallerIdentityCommand8 } from "@aws-sdk/client-sts";
29584
30296
  init_aws_clients();
29585
- async function stateMigrateBucketCommand(options) {
30297
+ async function stateMigrateCommand(options) {
29586
30298
  const logger = getLogger();
29587
30299
  if (options.verbose)
29588
30300
  logger.setLevel("debug");
@@ -29695,7 +30407,7 @@ async function stateMigrateBucketCommand(options) {
29695
30407
  }
29696
30408
  async function bucketExists2(s3, bucketName) {
29697
30409
  try {
29698
- await s3.send(new HeadBucketCommand3({ Bucket: bucketName }));
30410
+ await s3.send(new HeadBucketCommand4({ Bucket: bucketName }));
29699
30411
  return true;
29700
30412
  } catch (error) {
29701
30413
  const err = error;
@@ -29848,8 +30560,8 @@ async function confirmPrompt(prompt) {
29848
30560
  rl.close();
29849
30561
  }
29850
30562
  }
29851
- function createStateMigrateBucketCommand() {
29852
- const cmd = new Command9("migrate-bucket").description(
30563
+ function createStateMigrateCommand() {
30564
+ const cmd = new Command9("migrate").description(
29853
30565
  "Migrate state from the legacy region-suffixed bucket (cdkd-state-{account}-{region}) to the new region-free default (cdkd-state-{account}). Source bucket is kept by default; pass --remove-legacy to delete it after a successful migration."
29854
30566
  ).option(
29855
30567
  "--region <region>",
@@ -29864,7 +30576,7 @@ function createStateMigrateBucketCommand() {
29864
30576
  "--remove-legacy",
29865
30577
  "Delete the source bucket after successful migration. Default: keep it.",
29866
30578
  false
29867
- ).action(withErrorHandling(stateMigrateBucketCommand));
30579
+ ).action(withErrorHandling(stateMigrateCommand));
29868
30580
  commonOptions.forEach((o) => cmd.addOption(o));
29869
30581
  return cmd;
29870
30582
  }
@@ -30438,7 +31150,7 @@ function formatBucketSource(source) {
30438
31150
  case "default":
30439
31151
  return "default (account ID from STS)";
30440
31152
  case "default-legacy":
30441
- return "default (legacy region-suffixed name; cdkd state migrate-bucket recommended)";
31153
+ return "default (legacy region-suffixed name; cdkd state migrate recommended)";
30442
31154
  }
30443
31155
  }
30444
31156
  async function detectBucketRegion(awsClients, bucket) {
@@ -30553,9 +31265,344 @@ function createStateCommand() {
30553
31265
  cmd.addCommand(createStateShowCommand());
30554
31266
  cmd.addCommand(createStateRmCommand());
30555
31267
  cmd.addCommand(createStateDestroyCommand());
30556
- cmd.addCommand(createStateMigrateBucketCommand());
31268
+ cmd.addCommand(createStateMigrateCommand());
31269
+ return cmd;
31270
+ }
31271
+
31272
+ // src/cli/commands/import.ts
31273
+ import { readFileSync as readFileSync5 } from "node:fs";
31274
+ import * as readline4 from "node:readline/promises";
31275
+ import { Command as Command11 } from "commander";
31276
+ init_aws_clients();
31277
+ async function importCommand(stackArg, options) {
31278
+ const logger = getLogger();
31279
+ if (options.verbose) {
31280
+ logger.setLevel("debug");
31281
+ process.env["CDKD_NO_LIVE"] = "1";
31282
+ }
31283
+ const region = options.region || process.env["AWS_REGION"] || "us-east-1";
31284
+ const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
31285
+ if (options.region) {
31286
+ process.env["AWS_REGION"] = options.region;
31287
+ process.env["AWS_DEFAULT_REGION"] = options.region;
31288
+ }
31289
+ const awsClients = new AwsClients({
31290
+ ...options.region && { region: options.region },
31291
+ ...options.profile && { profile: options.profile }
31292
+ });
31293
+ setAwsClients(awsClients);
31294
+ try {
31295
+ const stateConfig = { bucket: stateBucket, prefix: options.statePrefix };
31296
+ const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
31297
+ ...options.region && { region: options.region },
31298
+ ...options.profile && { profile: options.profile }
31299
+ });
31300
+ await stateBackend.verifyBucketExists();
31301
+ const lockManager = new LockManager(awsClients.s3, stateConfig);
31302
+ const providerRegistry = new ProviderRegistry();
31303
+ registerAllProviders(providerRegistry);
31304
+ const appCmd = options.app || resolveApp();
31305
+ if (!appCmd) {
31306
+ throw new Error(
31307
+ "`cdkd state import` requires a CDK app: pass --app or set it in cdk.json. The template is read to find logical IDs, resource types, and dependencies."
31308
+ );
31309
+ }
31310
+ logger.info("Synthesizing CDK app to read template...");
31311
+ const synthesizer = new Synthesizer();
31312
+ const context = parseContextOptions(options.context);
31313
+ const result = await synthesizer.synthesize({
31314
+ app: appCmd,
31315
+ output: options.output || "cdk.out",
31316
+ ...Object.keys(context).length > 0 && { context }
31317
+ });
31318
+ let stackInfo;
31319
+ if (stackArg) {
31320
+ stackInfo = result.stacks.find((s) => s.stackName === stackArg || s.displayName === stackArg);
31321
+ if (!stackInfo) {
31322
+ throw new Error(
31323
+ `Stack '${stackArg}' not found in synthesized app. Available: ${result.stacks.map((s) => s.stackName).join(", ")}`
31324
+ );
31325
+ }
31326
+ } else if (result.stacks.length === 1) {
31327
+ stackInfo = result.stacks[0];
31328
+ } else {
31329
+ throw new Error(
31330
+ `Multiple stacks found: ${result.stacks.map((s) => s.stackName).join(", ")}. Specify the stack name as a positional argument.`
31331
+ );
31332
+ }
31333
+ const targetRegion = stackInfo.region || region;
31334
+ logger.info(`Target stack: ${stackInfo.stackName} (${targetRegion})`);
31335
+ const existing = await stateBackend.stateExists(stackInfo.stackName, targetRegion);
31336
+ if (existing && !options.force) {
31337
+ throw new Error(
31338
+ `State already exists for stack '${stackInfo.stackName}' (${targetRegion}). Pass --force to overwrite. (cdkd state import rebuilds the resource map from AWS, so the existing state \u2014 including any drift you've manually edited \u2014 will be lost.)`
31339
+ );
31340
+ }
31341
+ const overrides = parseResourceOverrides(options.resource, options.resourceMapping);
31342
+ if (overrides.size > 0) {
31343
+ logger.debug(`User-supplied physical IDs: ${[...overrides.keys()].join(", ")}`);
31344
+ }
31345
+ const template = stackInfo.template;
31346
+ const templateParser = new TemplateParser();
31347
+ const resources = collectImportableResources(template);
31348
+ logger.info(`Found ${resources.length} resource(s) in template`);
31349
+ const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
31350
+ await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "import");
31351
+ try {
31352
+ const rows = [];
31353
+ for (const { logicalId, resource } of resources) {
31354
+ const outcome = await importOne({
31355
+ logicalId,
31356
+ resource,
31357
+ stackName: stackInfo.stackName,
31358
+ region: targetRegion,
31359
+ providerRegistry,
31360
+ override: overrides.get(logicalId)
31361
+ });
31362
+ rows.push(outcome);
31363
+ }
31364
+ printSummary(rows);
31365
+ if (options.dryRun) {
31366
+ logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
31367
+ return;
31368
+ }
31369
+ const importedRows = rows.filter((r) => r.outcome === "imported");
31370
+ if (importedRows.length === 0) {
31371
+ logger.warn("No resources were successfully imported. State will not be written.");
31372
+ return;
31373
+ }
31374
+ if (!options.yes) {
31375
+ const ok = await confirmPrompt2(
31376
+ `Write state for ${stackInfo.stackName} (${targetRegion}) with ${importedRows.length} resource(s)?`
31377
+ );
31378
+ if (!ok) {
31379
+ logger.info("Import cancelled.");
31380
+ return;
31381
+ }
31382
+ }
31383
+ const stackState = buildStackState(
31384
+ stackInfo.stackName,
31385
+ targetRegion,
31386
+ rows,
31387
+ templateParser,
31388
+ template
31389
+ );
31390
+ await stateBackend.saveState(stackInfo.stackName, targetRegion, stackState);
31391
+ logger.info(`\u2713 State written: ${stackInfo.stackName} (${targetRegion})`);
31392
+ logger.info(
31393
+ ` ${importedRows.length} resource(s) imported. Run 'cdkd diff' to see how the imported state lines up with the template.`
31394
+ );
31395
+ } finally {
31396
+ await lockManager.releaseLock(stackInfo.stackName, targetRegion).catch((err) => {
31397
+ logger.warn(`Failed to release lock: ${err instanceof Error ? err.message : String(err)}`);
31398
+ });
31399
+ }
31400
+ } finally {
31401
+ awsClients.destroy();
31402
+ }
31403
+ }
31404
+ async function importOne(task) {
31405
+ const logger = getLogger();
31406
+ const { logicalId, resource, stackName, region, providerRegistry, override } = task;
31407
+ if (!providerRegistry.hasProvider(resource.Type)) {
31408
+ return {
31409
+ logicalId,
31410
+ resourceType: resource.Type,
31411
+ outcome: "skipped-no-impl",
31412
+ reason: "no provider registered"
31413
+ };
31414
+ }
31415
+ const provider = providerRegistry.getProvider(resource.Type);
31416
+ if (!provider.import) {
31417
+ return {
31418
+ logicalId,
31419
+ resourceType: resource.Type,
31420
+ outcome: "skipped-no-impl",
31421
+ reason: `provider does not implement import (yet)`
31422
+ };
31423
+ }
31424
+ const cdkPath = readCdkPath(resource);
31425
+ const input = {
31426
+ logicalId,
31427
+ resourceType: resource.Type,
31428
+ cdkPath,
31429
+ stackName,
31430
+ region,
31431
+ properties: resource.Properties ?? {},
31432
+ ...override !== void 0 && { knownPhysicalId: override }
31433
+ };
31434
+ try {
31435
+ const result = await provider.import(input);
31436
+ if (!result) {
31437
+ return {
31438
+ logicalId,
31439
+ resourceType: resource.Type,
31440
+ outcome: "skipped-not-found",
31441
+ reason: "no matching AWS resource"
31442
+ };
31443
+ }
31444
+ return {
31445
+ logicalId,
31446
+ resourceType: resource.Type,
31447
+ outcome: "imported",
31448
+ physicalId: result.physicalId
31449
+ };
31450
+ } catch (error) {
31451
+ const msg = error instanceof Error ? error.message : String(error);
31452
+ logger.error(`Failed to import ${logicalId} (${resource.Type}): ${msg}`);
31453
+ return {
31454
+ logicalId,
31455
+ resourceType: resource.Type,
31456
+ outcome: "failed",
31457
+ reason: msg
31458
+ };
31459
+ }
31460
+ }
31461
+ function parseResourceOverrides(flags, mappingFile) {
31462
+ const map = /* @__PURE__ */ new Map();
31463
+ if (mappingFile) {
31464
+ let parsed;
31465
+ try {
31466
+ parsed = JSON.parse(readFileSync5(mappingFile, "utf-8"));
31467
+ } catch (err) {
31468
+ throw new Error(
31469
+ `Failed to read --resource-mapping file '${mappingFile}': ` + (err instanceof Error ? err.message : String(err))
31470
+ );
31471
+ }
31472
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31473
+ throw new Error(
31474
+ `--resource-mapping file '${mappingFile}' must be a JSON object {logicalId: physicalId}`
31475
+ );
31476
+ }
31477
+ for (const [key, value] of Object.entries(parsed)) {
31478
+ if (typeof value !== "string") {
31479
+ throw new Error(
31480
+ `--resource-mapping: value for '${key}' must be a string, got ${typeof value}`
31481
+ );
31482
+ }
31483
+ map.set(key, value);
31484
+ }
31485
+ }
31486
+ for (const entry of flags ?? []) {
31487
+ const eq = entry.indexOf("=");
31488
+ if (eq <= 0 || eq === entry.length - 1) {
31489
+ throw new Error(`--resource expects 'logicalId=physicalId', got '${entry}'`);
31490
+ }
31491
+ map.set(entry.slice(0, eq), entry.slice(eq + 1));
31492
+ }
31493
+ return map;
31494
+ }
31495
+ function readCdkPath(resource) {
31496
+ const meta = resource.Metadata;
31497
+ if (!meta)
31498
+ return "";
31499
+ const v = meta["aws:cdk:path"];
31500
+ return typeof v === "string" ? v : "";
31501
+ }
31502
+ function collectImportableResources(template) {
31503
+ const out = [];
31504
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
31505
+ if (resource.Type === "AWS::CDK::Metadata")
31506
+ continue;
31507
+ out.push({ logicalId, resource });
31508
+ }
31509
+ return out;
31510
+ }
31511
+ function buildStackState(stackName, region, rows, templateParser, template) {
31512
+ const resources = {};
31513
+ for (const row of rows) {
31514
+ if (row.outcome !== "imported" || !row.physicalId)
31515
+ continue;
31516
+ const tmplResource = template.Resources[row.logicalId];
31517
+ if (!tmplResource)
31518
+ continue;
31519
+ const deps = templateParser.extractDependencies(tmplResource);
31520
+ resources[row.logicalId] = {
31521
+ physicalId: row.physicalId,
31522
+ resourceType: row.resourceType,
31523
+ properties: tmplResource.Properties ?? {},
31524
+ attributes: {},
31525
+ dependencies: [...deps]
31526
+ };
31527
+ }
31528
+ return {
31529
+ version: 2,
31530
+ stackName,
31531
+ region,
31532
+ resources,
31533
+ outputs: {},
31534
+ lastModified: Date.now()
31535
+ };
31536
+ }
31537
+ function printSummary(rows) {
31538
+ const logger = getLogger();
31539
+ const counts = {
31540
+ imported: 0,
31541
+ "skipped-no-impl": 0,
31542
+ "skipped-not-found": 0,
31543
+ failed: 0
31544
+ };
31545
+ logger.info("");
31546
+ logger.info("Import plan:");
31547
+ for (const r of rows) {
31548
+ counts[r.outcome]++;
31549
+ const tag = formatOutcome(r.outcome);
31550
+ const detail = r.outcome === "imported" ? ` (${r.physicalId})` : r.reason ? ` \u2014 ${r.reason}` : "";
31551
+ logger.info(` ${tag} ${r.logicalId} (${r.resourceType})${detail}`);
31552
+ }
31553
+ logger.info("");
31554
+ logger.info(
31555
+ `Summary: ${counts.imported} imported, ${counts["skipped-not-found"]} not found, ${counts["skipped-no-impl"]} unsupported, ${counts.failed} failed`
31556
+ );
31557
+ }
31558
+ function formatOutcome(outcome) {
31559
+ switch (outcome) {
31560
+ case "imported":
31561
+ return "\u2713";
31562
+ case "skipped-not-found":
31563
+ return "\xB7";
31564
+ case "skipped-no-impl":
31565
+ return "?";
31566
+ case "failed":
31567
+ return "\u2717";
31568
+ }
31569
+ }
31570
+ async function confirmPrompt2(prompt) {
31571
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
31572
+ try {
31573
+ const ans = await rl.question(`${prompt} [y/N] `);
31574
+ return /^y(es)?$/i.test(ans.trim());
31575
+ } finally {
31576
+ rl.close();
31577
+ }
31578
+ }
31579
+ function createImportCommand() {
31580
+ const cmd = new Command11("import").description(
31581
+ "Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies; uses the aws:cdk:path tag (or explicit --resource overrides) to find each resource in AWS."
31582
+ ).argument(
31583
+ "[stack]",
31584
+ "Stack to import. Optional when the synthesized app contains exactly one stack."
31585
+ ).option(
31586
+ "--resource <id=physical>",
31587
+ "Explicit physical-id override for one logical ID. Repeatable. Bypasses tag-based auto-lookup for that resource only.",
31588
+ collectMultiple,
31589
+ []
31590
+ ).option(
31591
+ "--resource-mapping <file>",
31592
+ "Path to a JSON file of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping` compatible)."
31593
+ ).option("--dry-run", "Show planned imports without writing state", false).option(
31594
+ "--force",
31595
+ "Overwrite an existing state record. Without this, an existing state file aborts the import.",
31596
+ false
31597
+ ).action(withErrorHandling(importCommand));
31598
+ [...commonOptions, ...appOptions, ...stateOptions, ...contextOptions].forEach(
31599
+ (o) => cmd.addOption(o)
31600
+ );
30557
31601
  return cmd;
30558
31602
  }
31603
+ function collectMultiple(value, previous) {
31604
+ return [...previous ?? [], value];
31605
+ }
30559
31606
 
30560
31607
  // src/cli/index.ts
30561
31608
  var SUBCOMMANDS = /* @__PURE__ */ new Set([
@@ -30566,6 +31613,7 @@ var SUBCOMMANDS = /* @__PURE__ */ new Set([
30566
31613
  "deploy",
30567
31614
  "diff",
30568
31615
  "destroy",
31616
+ "import",
30569
31617
  "publish-assets",
30570
31618
  "force-unlock",
30571
31619
  "state"
@@ -30581,14 +31629,15 @@ function reorderArgs(argv) {
30581
31629
  return [...prefix, ...cmdAndAfter, ...beforeCmd];
30582
31630
  }
30583
31631
  async function main() {
30584
- const program = new Command11();
30585
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.14.0");
31632
+ const program = new Command12();
31633
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.16.0");
30586
31634
  program.addCommand(createBootstrapCommand());
30587
31635
  program.addCommand(createSynthCommand());
30588
31636
  program.addCommand(createListCommand());
30589
31637
  program.addCommand(createDeployCommand());
30590
31638
  program.addCommand(createDiffCommand());
30591
31639
  program.addCommand(createDestroyCommand());
31640
+ program.addCommand(createImportCommand());
30592
31641
  program.addCommand(createPublishAssetsCommand());
30593
31642
  program.addCommand(createForceUnlockCommand());
30594
31643
  program.addCommand(createStateCommand());