@go-to-k/cdkd 0.257.0 → 0.257.1

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/README.md CHANGED
@@ -452,7 +452,9 @@ the stack is managed by `cdkd deploy`. This is the reverse direction
452
452
  of `cdkd export` (see below).
453
453
 
454
454
  ```bash
455
- # Adopt a whole stack previously deployed by cdk deploy (tag-based auto-lookup).
455
+ # Adopt a whole stack: each resource is resolved from its template name property,
456
+ # then from a same-named CloudFormation stack (#1128). Use
457
+ # --migrate-from-cloudformation below when you also want that stack retired.
456
458
  cdkd import MyStack --yes
457
459
 
458
460
  # Adopt only specific resources (CDK CLI parity).
package/dist/cli.js CHANGED
@@ -1899,7 +1899,7 @@ const FLUSH_INTERVAL_MS = 2e3;
1899
1899
  const FLUSH_EVENT_THRESHOLD = 50;
1900
1900
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
1901
1901
  function getCdkdVersion() {
1902
- return "0.257.0";
1902
+ return "0.257.1";
1903
1903
  }
1904
1904
  /**
1905
1905
  * Generate a time-sortable unique run id, e.g.
@@ -3945,9 +3945,11 @@ var IAMInstanceProfileProvider = class {
3945
3945
  * Lookup order:
3946
3946
  * 1. `--resource` override or `Properties.InstanceProfileName` → verify
3947
3947
  * via `GetInstanceProfile`.
3948
- * 2. `ListInstanceProfiles` paginator + match `aws:cdk:path` against the
3949
- * `InstanceProfile.Tags` array returned inline (no separate
3950
- * `ListInstanceProfileTags` call needed).
3948
+ * 2. `ListInstanceProfiles` paginator + a per-candidate `GetInstanceProfile`
3949
+ * to read the tags, matched against `aws:cdk:path`. The list API does
3950
+ * NOT return tags (AWS documents this on the command) even though the
3951
+ * `InstanceProfile` type declares `Tags?`, so reading them off the
3952
+ * summary typechecks and never matches.
3951
3953
  *
3952
3954
  * IAM is global; this walks every instance profile in the account once.
3953
3955
  */
@@ -3963,20 +3965,32 @@ var IAMInstanceProfileProvider = class {
3963
3965
  if (err instanceof NoSuchEntityException) return null;
3964
3966
  throw err;
3965
3967
  }
3966
- if (!input.cdkPath) return null;
3967
- let marker;
3968
- do {
3969
- const list = await this.iamClient.send(new ListInstanceProfilesCommand({ ...marker && { Marker: marker } }));
3970
- for (const profile of list.InstanceProfiles ?? []) {
3971
- if (!profile.InstanceProfileName) continue;
3972
- if (matchesCdkPath(profile.Tags, input.cdkPath)) return {
3973
- physicalId: profile.InstanceProfileName,
3974
- attributes: {}
3968
+ const match = await importTagWalk({
3969
+ cdkPath: input.cdkPath,
3970
+ logicalId: input.logicalId,
3971
+ listPage: async (marker) => {
3972
+ const list = await this.iamClient.send(new ListInstanceProfilesCommand({ ...marker && { Marker: marker } }));
3973
+ return {
3974
+ items: list.InstanceProfiles,
3975
+ nextMarker: list.IsTruncated ? list.Marker : void 0
3975
3976
  };
3976
- }
3977
- marker = list.IsTruncated ? list.Marker : void 0;
3978
- } while (marker);
3979
- return null;
3977
+ },
3978
+ describe: async (profile) => {
3979
+ if (!profile.InstanceProfileName) return void 0;
3980
+ try {
3981
+ return (await this.iamClient.send(new GetInstanceProfileCommand({ InstanceProfileName: profile.InstanceProfileName }))).InstanceProfile;
3982
+ } catch (err) {
3983
+ if (err instanceof NoSuchEntityException) return void 0;
3984
+ throw err;
3985
+ }
3986
+ },
3987
+ tagsOf: (detail) => detail?.Tags
3988
+ });
3989
+ if (!match) return null;
3990
+ return {
3991
+ physicalId: match.summary.InstanceProfileName,
3992
+ attributes: {}
3993
+ };
3980
3994
  }
3981
3995
  };
3982
3996
 
@@ -10379,25 +10393,35 @@ var LambdaLayerVersionProvider = class {
10379
10393
  if (err instanceof ResourceNotFoundException) return null;
10380
10394
  throw err;
10381
10395
  }
10382
- if (!input.cdkPath) return null;
10383
- let marker;
10384
- do {
10385
- const list = await this.lambdaClient.send(new ListLayersCommand({ ...marker && { Marker: marker } }));
10386
- for (const layer of list.Layers ?? []) {
10387
- if (!layer.LayerArn || !layer.LatestMatchingVersion?.LayerVersionArn) continue;
10396
+ const match = await importTagWalk({
10397
+ cdkPath: input.cdkPath,
10398
+ logicalId: input.logicalId,
10399
+ listPage: async (marker) => {
10400
+ const list = await this.lambdaClient.send(new ListLayersCommand({ ...marker && { Marker: marker } }));
10401
+ return {
10402
+ items: list.Layers,
10403
+ nextMarker: list.NextMarker
10404
+ };
10405
+ },
10406
+ describe: async (layer) => {
10407
+ if (!layer.LayerArn || !layer.LatestMatchingVersion?.LayerVersionArn) return void 0;
10388
10408
  try {
10389
- if ((await this.lambdaClient.send(new ListTagsCommand({ Resource: layer.LayerArn }))).Tags?.["aws:cdk:path"] === input.cdkPath) return {
10390
- physicalId: layer.LatestMatchingVersion.LayerVersionArn,
10391
- attributes: {}
10392
- };
10409
+ return await this.lambdaClient.send(new ListTagsCommand({ Resource: layer.LayerArn }));
10393
10410
  } catch (err) {
10394
- if (err instanceof ResourceNotFoundException) continue;
10411
+ if (err instanceof ResourceNotFoundException) return void 0;
10395
10412
  throw err;
10396
10413
  }
10397
- }
10398
- marker = list.NextMarker;
10399
- } while (marker);
10400
- return null;
10414
+ },
10415
+ tagsOf: (tagsResp) => Object.entries(tagsResp.Tags ?? {}).map(([Key, Value]) => ({
10416
+ Key,
10417
+ Value
10418
+ }))
10419
+ });
10420
+ if (!match) return null;
10421
+ return {
10422
+ physicalId: match.summary.LatestMatchingVersion.LayerVersionArn,
10423
+ attributes: {}
10424
+ };
10401
10425
  }
10402
10426
  };
10403
10427
 
@@ -13940,17 +13964,24 @@ var SecretsManagerSecretProvider = class {
13940
13964
  if (err instanceof ResourceNotFoundException$3) return null;
13941
13965
  throw err;
13942
13966
  }
13943
- if (!input.cdkPath) return null;
13944
- let nextToken;
13945
- do {
13946
- const list = await this.smClient.send(new ListSecretsCommand({ ...nextToken && { NextToken: nextToken } }));
13947
- for (const s of list.SecretList ?? []) if (s.ARN && matchesCdkPath(s.Tags, input.cdkPath)) return {
13948
- physicalId: s.ARN,
13949
- attributes: {}
13950
- };
13951
- nextToken = list.NextToken;
13952
- } while (nextToken);
13953
- return null;
13967
+ const match = await importTagWalk({
13968
+ cdkPath: input.cdkPath,
13969
+ logicalId: input.logicalId,
13970
+ listPage: async (marker) => {
13971
+ const list = await this.smClient.send(new ListSecretsCommand({ ...marker && { NextToken: marker } }));
13972
+ return {
13973
+ items: list.SecretList,
13974
+ nextMarker: list.NextToken
13975
+ };
13976
+ },
13977
+ describe: async (secret) => secret.ARN ? secret : void 0,
13978
+ tagsOf: (secret) => secret.Tags
13979
+ });
13980
+ if (!match) return null;
13981
+ return {
13982
+ physicalId: match.summary.ARN,
13983
+ attributes: {}
13984
+ };
13954
13985
  }
13955
13986
  };
13956
13987
 
@@ -32552,25 +32583,35 @@ var KMSProvider = class {
32552
32583
  throw err;
32553
32584
  }
32554
32585
  if (!input.cdkPath) return null;
32555
- let marker;
32556
- do {
32557
- const list = await this.getClient().send(new ListKeysCommand({ ...marker && { Marker: marker } }));
32558
- for (const key of list.Keys ?? []) {
32559
- if (!key.KeyId) continue;
32586
+ const match = await importTagWalk({
32587
+ cdkPath: input.cdkPath,
32588
+ logicalId: input.logicalId,
32589
+ listPage: async (marker) => {
32590
+ const list = await this.getClient().send(new ListKeysCommand({ ...marker && { Marker: marker } }));
32591
+ return {
32592
+ items: list.Keys,
32593
+ nextMarker: list.NextMarker
32594
+ };
32595
+ },
32596
+ describe: async (key) => {
32597
+ if (!key.KeyId) return void 0;
32560
32598
  try {
32561
- const tagsResp = await this.getClient().send(new ListResourceTagsCommand({ KeyId: key.KeyId }));
32562
- for (const tag of tagsResp.Tags ?? []) if (tag.TagKey === "aws:cdk:path" && tag.TagValue === input.cdkPath) return {
32563
- physicalId: key.KeyId,
32564
- attributes: {}
32565
- };
32599
+ return await this.getClient().send(new ListResourceTagsCommand({ KeyId: key.KeyId }));
32566
32600
  } catch (err) {
32567
- if (err.name === "AccessDeniedException" || err instanceof NotFoundException$2) continue;
32601
+ if (err.name === "AccessDeniedException" || err instanceof NotFoundException$2) return void 0;
32568
32602
  throw err;
32569
32603
  }
32570
- }
32571
- marker = list.NextMarker;
32572
- } while (marker);
32573
- return null;
32604
+ },
32605
+ tagsOf: (tagsResp) => (tagsResp.Tags ?? []).map((t) => ({
32606
+ Key: t.TagKey,
32607
+ Value: t.TagValue
32608
+ }))
32609
+ });
32610
+ if (!match) return null;
32611
+ return {
32612
+ physicalId: match.summary.KeyId,
32613
+ attributes: {}
32614
+ };
32574
32615
  }
32575
32616
  };
32576
32617
 
@@ -39687,28 +39728,42 @@ var CodeCommitRepositoryProvider = class {
39687
39728
  if (err instanceof RepositoryDoesNotExistException) return null;
39688
39729
  throw err;
39689
39730
  }
39690
- if (!input.cdkPath) return null;
39691
- let nextToken;
39692
- do {
39693
- const list = await this.getClient().send(new ListRepositoriesCommand({ ...nextToken && { nextToken } }));
39694
- for (const repo of list.repositories ?? []) {
39695
- if (!repo.repositoryName) continue;
39731
+ const match = await importTagWalk({
39732
+ cdkPath: input.cdkPath,
39733
+ logicalId: input.logicalId,
39734
+ listPage: async (marker) => {
39735
+ const list = await this.getClient().send(new ListRepositoriesCommand({ ...marker && { nextToken: marker } }));
39736
+ return {
39737
+ items: list.repositories,
39738
+ nextMarker: list.nextToken
39739
+ };
39740
+ },
39741
+ describe: async (repo) => {
39742
+ if (!repo.repositoryName) return void 0;
39696
39743
  let metadata;
39697
39744
  try {
39698
39745
  metadata = await this.getRepositoryMetadata(repo.repositoryName);
39699
39746
  } catch (err) {
39700
- if (err instanceof RepositoryDoesNotExistException) continue;
39747
+ if (err instanceof RepositoryDoesNotExistException) return void 0;
39701
39748
  throw err;
39702
39749
  }
39703
- if (!metadata?.Arn) continue;
39704
- if ((await this.getClient().send(new ListTagsForResourceCommand$19({ resourceArn: metadata.Arn }))).tags?.["aws:cdk:path"] === input.cdkPath) return {
39705
- physicalId: repo.repositoryName,
39706
- attributes: this.toAttributes(metadata)
39750
+ if (!metadata?.Arn) return void 0;
39751
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$19({ resourceArn: metadata.Arn }));
39752
+ return {
39753
+ metadata,
39754
+ tags: tagsResp.tags
39707
39755
  };
39708
- }
39709
- nextToken = list.nextToken;
39710
- } while (nextToken);
39711
- return null;
39756
+ },
39757
+ tagsOf: (detail) => Object.entries(detail.tags ?? {}).map(([Key, Value]) => ({
39758
+ Key,
39759
+ Value
39760
+ }))
39761
+ });
39762
+ if (!match) return null;
39763
+ return {
39764
+ physicalId: match.summary.repositoryName,
39765
+ attributes: this.toAttributes(match.detail.metadata)
39766
+ };
39712
39767
  }
39713
39768
  /**
39714
39769
  * Seed the repository's initial commit from the CFn `Code` property.
@@ -40038,16 +40093,23 @@ var DLMLifecyclePolicyProvider = class {
40038
40093
  if (err instanceof ResourceNotFoundException$12) return null;
40039
40094
  throw err;
40040
40095
  }
40041
- if (!input.cdkPath) return null;
40042
- const list = await this.getClient().send(new GetLifecyclePoliciesCommand({}));
40043
- for (const policy of list.Policies ?? []) {
40044
- if (!policy.PolicyId) continue;
40045
- if (policy.Tags?.["aws:cdk:path"] === input.cdkPath) return {
40046
- physicalId: policy.PolicyId,
40047
- attributes: {}
40048
- };
40049
- }
40050
- return null;
40096
+ const match = await importTagWalk({
40097
+ cdkPath: input.cdkPath,
40098
+ logicalId: input.logicalId,
40099
+ listPage: async () => {
40100
+ return { items: (await this.getClient().send(new GetLifecyclePoliciesCommand({}))).Policies };
40101
+ },
40102
+ describe: async (policy) => policy.PolicyId ? policy : void 0,
40103
+ tagsOf: (policy) => Object.entries(policy.Tags ?? {}).map(([Key, Value]) => ({
40104
+ Key,
40105
+ Value
40106
+ }))
40107
+ });
40108
+ if (!match) return null;
40109
+ return {
40110
+ physicalId: match.summary.PolicyId,
40111
+ attributes: {}
40112
+ };
40051
40113
  }
40052
40114
  };
40053
40115
 
@@ -40285,25 +40347,35 @@ var S3VectorsProvider = class {
40285
40347
  if (this.isNotFoundError(err)) return null;
40286
40348
  throw err;
40287
40349
  }
40288
- if (!input.cdkPath) return null;
40289
- let token;
40290
- do {
40291
- const list = await this.getClient().send(new ListVectorBucketsCommand({ ...token && { nextToken: token } }));
40292
- for (const bucket of list.vectorBuckets ?? []) {
40293
- if (!bucket.vectorBucketName || !bucket.vectorBucketArn) continue;
40350
+ const match = await importTagWalk({
40351
+ cdkPath: input.cdkPath,
40352
+ logicalId: input.logicalId,
40353
+ listPage: async (marker) => {
40354
+ const list = await this.getClient().send(new ListVectorBucketsCommand({ ...marker && { nextToken: marker } }));
40355
+ return {
40356
+ items: list.vectorBuckets,
40357
+ nextMarker: list.nextToken
40358
+ };
40359
+ },
40360
+ describe: async (bucket) => {
40361
+ if (!bucket.vectorBucketName || !bucket.vectorBucketArn) return void 0;
40294
40362
  try {
40295
- if ((await this.getClient().send(new ListTagsForResourceCommand$20({ resourceArn: bucket.vectorBucketArn }))).tags?.["aws:cdk:path"] === input.cdkPath) return {
40296
- physicalId: bucket.vectorBucketName,
40297
- attributes: {}
40298
- };
40363
+ return await this.getClient().send(new ListTagsForResourceCommand$20({ resourceArn: bucket.vectorBucketArn }));
40299
40364
  } catch (err) {
40300
- if (this.isNotFoundError(err)) continue;
40365
+ if (this.isNotFoundError(err)) return void 0;
40301
40366
  throw err;
40302
40367
  }
40303
- }
40304
- token = list.nextToken;
40305
- } while (token);
40306
- return null;
40368
+ },
40369
+ tagsOf: (tagsResp) => Object.entries(tagsResp.tags ?? {}).map(([Key, Value]) => ({
40370
+ Key,
40371
+ Value
40372
+ }))
40373
+ });
40374
+ if (!match) return null;
40375
+ return {
40376
+ physicalId: match.summary.vectorBucketName,
40377
+ attributes: {}
40378
+ };
40307
40379
  }
40308
40380
  isNotFoundError(error) {
40309
40381
  if (error instanceof Error) {
@@ -44179,6 +44251,67 @@ async function confirmPrompt$6(prompt) {
44179
44251
  rl.close();
44180
44252
  }
44181
44253
  }
44254
+ /**
44255
+ * Flat `logicalId -> physicalId` map for ONE CloudFormation stack, or `null`
44256
+ * when no stack of that name exists.
44257
+ *
44258
+ * This is the non-recursive, non-throwing sibling of
44259
+ * {@link getCloudFormationResourceTree}, used by `cdkd import`'s auto mode as
44260
+ * a best-effort lookup (issue #1128). The distinction matters:
44261
+ *
44262
+ * - the tree walk is for `--migrate-from-cloudformation`, where the stack is
44263
+ * known to exist and nested children must be walked so each child's state
44264
+ * can be written and the whole tree retired;
44265
+ * - this one runs speculatively on every auto-mode import, so a missing
44266
+ * stack is the NORMAL case (a cdkd-native stack has no CFn counterpart)
44267
+ * and must return `null` rather than abort the import.
44268
+ *
44269
+ * NO failure is fatal. The lookup is an OPTIMIZATION: when it cannot answer,
44270
+ * the caller falls through to the per-provider lookups, which is exactly the
44271
+ * behavior that shipped before this function existed. Throwing would convert a
44272
+ * missed improvement into a hard failure — and it would do so precisely for the
44273
+ * users least likely to benefit. A cdkd-native stack has no CloudFormation
44274
+ * counterpart, so an operator whose IAM policy is scoped to cdkd's actual needs
44275
+ * (no `cloudformation:DescribeStackResources`) would see `AccessDenied` abort an
44276
+ * import that worked fine the day before.
44277
+ *
44278
+ * A missing stack is the expected case and logs at debug. Anything else logs a
44279
+ * WARN naming the cause, so a permissions gap or a throttle is visible rather
44280
+ * than silent — the user learns why the ids were not resolved, and still gets
44281
+ * the pre-existing behavior instead of an abort.
44282
+ */
44283
+ async function tryGetCloudFormationResourceMap(stackName, cfnClient) {
44284
+ const logger = getLogger();
44285
+ let resp;
44286
+ try {
44287
+ resp = await cfnClient.send(new DescribeStackResourcesCommand({ StackName: stackName }));
44288
+ } catch (err) {
44289
+ if (isStackNotFoundError(err)) {
44290
+ logger.debug(`No CloudFormation stack named '${stackName}'.`);
44291
+ return null;
44292
+ }
44293
+ const reason = err instanceof Error ? err.message : String(err);
44294
+ logger.warn(`Could not read CloudFormation stack '${stackName}' to resolve physical IDs (${reason}). Falling back to per-resource lookup; resources whose physical name CloudFormation generated may be reported as not found. Grant cloudformation:DescribeStackResources, or pass --resource <LogicalId>=<physicalId> for those resources.`);
44295
+ return null;
44296
+ }
44297
+ const resources = /* @__PURE__ */ new Map();
44298
+ for (const r of resp.StackResources ?? []) {
44299
+ if (!r.LogicalResourceId || !r.PhysicalResourceId) continue;
44300
+ resources.set(r.LogicalResourceId, r.PhysicalResourceId);
44301
+ }
44302
+ return resources;
44303
+ }
44304
+ /**
44305
+ * CloudFormation reports an unknown stack as a `ValidationError` whose message
44306
+ * carries "does not exist" — there is no dedicated exception type to catch.
44307
+ * Matching the message is therefore required, but it is kept narrow so an
44308
+ * unrelated ValidationError (a malformed stack name, say) still surfaces.
44309
+ */
44310
+ function isStackNotFoundError(err) {
44311
+ const name = err?.name;
44312
+ const message = err?.message ?? "";
44313
+ return name === "ValidationError" && /does not exist/i.test(message);
44314
+ }
44182
44315
 
44183
44316
  //#endregion
44184
44317
  //#region src/cli/commands/diff-recursive.ts
@@ -51905,6 +52038,21 @@ async function importCommand(stackArg, options) {
51905
52038
  }
51906
52039
  const selectiveMode = overrides.size > 0 && !options.auto && !options.migrateFromCloudformation;
51907
52040
  if (selectiveMode) logger.info(`Selective mode: only importing the ${overrides.size} resource(s) you listed (${[...overrides.keys()].join(", ")}). Pass --auto to also tag-import the rest.`);
52041
+ if (!selectiveMode && !migrationCfnStackName) {
52042
+ const cfnResources = await tryGetCloudFormationResourceMap(stackInfo.stackName, awsClients.cloudFormation);
52043
+ if (cfnResources) {
52044
+ let derived = 0;
52045
+ for (const [logicalId, physicalId] of cfnResources) {
52046
+ if (!templateLogicalIds.has(logicalId)) continue;
52047
+ if (template.Resources[logicalId]?.Type === "AWS::CloudFormation::Stack") continue;
52048
+ if (overrides.has(logicalId)) continue;
52049
+ overrides.set(logicalId, physicalId);
52050
+ derived++;
52051
+ }
52052
+ if (derived > 0) logger.info(`Resolved ${derived} physical ID(s) from CloudFormation stack '${stackInfo.stackName}'. To adopt AND retire that stack, use --migrate-from-cloudformation.`);
52053
+ else logger.debug(`CloudFormation stack '${stackInfo.stackName}' contributed no new physical IDs.`);
52054
+ } else logger.debug(`No CloudFormation stack named '${stackInfo.stackName}' — resolving physical IDs per provider.`);
52055
+ }
51908
52056
  const existingResult = await stateBackend.getState(stackInfo.stackName, targetRegion);
51909
52057
  const existingState = existingResult?.state ?? null;
51910
52058
  const existingEtag = existingResult?.etag;
@@ -62172,7 +62320,7 @@ function createMigrateCommand() {
62172
62320
  */
62173
62321
  function buildProgram() {
62174
62322
  const program = new Command();
62175
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.257.0");
62323
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.257.1");
62176
62324
  program.addCommand(createBootstrapCommand());
62177
62325
  program.addCommand(createSynthCommand());
62178
62326
  program.addCommand(createListCommand());