@go-to-k/cdkd 0.93.0 → 0.94.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
@@ -455,44 +455,82 @@ Both `cdkd destroy` (synth-driven) and `cdkd state destroy`
455
455
 
456
456
  ## Stack-name prefix on physical names
457
457
 
458
- cdkd prepends the **stack name** to physical names you declare in CDK
459
- code: `new iam.Role(this, 'CRRole', { roleName: 'my-role' })` in stack
460
- `MyStack` is created in AWS as `MyStack-my-role`. The prefix protects
461
- cross-stack uniqueness (two stacks declaring `roleName: 'my-role'`
462
- otherwise collide on a single AWS account). Pre-PR this behavior was
463
- **inconsistent**: only IAM Role / User / Group / InstanceProfile and
464
- ELBv2 LoadBalancer / TargetGroup actually got the prefix; Lambda, S3,
465
- SNS, SQS, DynamoDB, etc. used the user's declared name as-is.
466
-
467
- `cdkd deploy --no-prefix-user-supplied-names` opts in to skipping
468
- the prefix on user-declared physical names, making cdkd consistent
469
- across all resource types. Off by default for backward compatibility.
470
-
471
- | | Default (no flag) | `--no-prefix-user-supplied-names` |
458
+ By default cdkd creates AWS resources with the **exact name you
459
+ declared** in CDK code: `new iam.Role(this, 'CRRole', { roleName:
460
+ 'my-role' })` in stack `MyStack` produces an AWS resource named
461
+ `my-role`. Consistent across every resource type. This is the
462
+ default since **v0.94.0** (closes [#299](https://github.com/go-to-k/cdkd/issues/299)).
463
+
464
+ Pre-v0.94.0 cdkd prepended the stack name to user-declared physical
465
+ names on a subset of types only (Pattern B providers: IAM Role /
466
+ User / Group / InstanceProfile / ELBv2 LoadBalancer / TargetGroup),
467
+ while Pattern A providers (Lambda, S3, SNS, SQS, DynamoDB, etc.) used
468
+ the user's name as-is. The inconsistency was opaque to users and
469
+ surfaced as failures in `cdkd export` (CFn IMPORT identifier
470
+ mismatch). Flipping the default brings every resource type into line
471
+ out of the box.
472
+
473
+ `cdkd deploy --prefix-user-supplied-names` opts BACK in to the
474
+ legacy prefixing on Pattern B providers (matching pre-v0.94.0 cdkd).
475
+ Useful when migrating an existing stack that was originally deployed
476
+ under the legacy default and you don't want to take a one-time
477
+ replacement on every Pattern B resource.
478
+
479
+ | | Default (no flag) | `--prefix-user-supplied-names` |
472
480
  | --- | --- | --- |
473
- | `new iam.Role({ roleName: 'my-role' })` | `MyStack-my-role` | `my-role` |
474
- | `new s3.Bucket({ bucketName: 'my-bucket' })` | `my-bucket` (already no prefix — Pattern A) | `my-bucket` (unchanged) |
475
- | `new iam.Role(...)` (no `roleName`) | `MyStack-CRRole-<hash>` (auto-generated, prefix kept for uniqueness) | `MyStack-CRRole-<hash>` (unchanged) |
476
-
477
- Resolution chain (highest wins): `--no-prefix-user-supplied-names`
478
- CLI flag → `CDKD_NO_PREFIX_USER_SUPPLIED_NAMES=true` env var →
479
- `cdk.json` `context.cdkd.noPrefixUserSuppliedNames: true` → default
480
- `false`.
481
-
482
- Affects `cdkd deploy` only. Already-deployed stacks deployed under
483
- the legacy prefixed-name scheme keep working — the flag only controls
484
- what AWS resource cdkd creates on **future** deploys. Switching the
485
- flag mid-flight on an existing stack would propose REPLACEMENT on
486
- every Pattern B resource (the existing AWS resource has the prefixed
487
- name; the new template intent has the un-prefixed name).
488
-
489
- Surfaced by [PR #285 `cdkd export`](https://github.com/go-to-k/cdkd/pull/285)
490
- where the CFn IMPORT changeset's identifier check would fail on a
491
- synth `RoleName: 'my-role'` vs the AWS-deployed `MyStack-my-role`;
492
- the export command currently overlays `ResourceIdentifier` onto
493
- `Properties` to bridge the gap. A future major-version PR will flip
494
- the default of `--no-prefix-user-supplied-names` to `true`, after
495
- which the overlay can be dropped.
481
+ | `new iam.Role({ roleName: 'my-role' })` | `my-role` | `MyStack-my-role` (legacy) |
482
+ | `new s3.Bucket({ bucketName: 'my-bucket' })` | `my-bucket` (always — Pattern A) | `my-bucket` (unchanged) |
483
+ | `new iam.Role(...)` (no `roleName`) | `MyStack-CRRole-<hash>` (auto-generated; prefix kept for uniqueness) | `MyStack-CRRole-<hash>` (unchanged) |
484
+
485
+ Resolution chain (highest wins): `--prefix-user-supplied-names`
486
+ CLI flag → `CDKD_PREFIX_USER_SUPPLIED_NAMES=true` env var →
487
+ `cdk.json` `context.cdkd.prefixUserSuppliedNames: true` → default
488
+ `false` (skip prefix).
489
+
490
+ The deprecated `--no-prefix-user-supplied-names` flag (plus the
491
+ `CDKD_NO_PREFIX_USER_SUPPLIED_NAMES` env var and `cdk.json
492
+ context.cdkd.noPrefixUserSuppliedNames`) is still accepted but now
493
+ matches the default; setting it emits a deprecation warning and is a
494
+ no-op. Remove it from your CLI invocations and config.
495
+
496
+ ### Migration from pre-v0.94.0
497
+
498
+ This is a **breaking change**: upgrading from a pre-v0.94.0 cdkd to
499
+ v0.94.0+ flips the AWS-resource name cdkd produces on Pattern B
500
+ providers (IAM Role / User / Group / InstanceProfile / ELBv2 LB / TG)
501
+ with user-supplied physical names. The next `cdkd deploy` against an
502
+ existing stack will propose REPLACEMENT on every such resource —
503
+ the AWS resource has the prefixed name; the new template intent has
504
+ the un-prefixed name.
505
+
506
+ Pick one of:
507
+
508
+ 1. **Accept the one-time replacement** (simplest; only safe when the
509
+ types involved tolerate replacement — IAM Roles get fresh ARNs,
510
+ ELBv2 LBs get fresh DNS names).
511
+ 2. **Pin legacy prefixing**: pass `--prefix-user-supplied-names`,
512
+ set `CDKD_PREFIX_USER_SUPPLIED_NAMES=true`, or add
513
+ `"prefixUserSuppliedNames": true` under `cdk.json` `context.cdkd`.
514
+ 3. **Drop the explicit physical name** in CDK code where you don't
515
+ actually need a stable name — `new iam.Role(...)` without
516
+ `roleName` always uses the auto-generated `MyStack-CRRole-<hash>`
517
+ form regardless of this flag.
518
+
519
+ A migration helper (`cdkd state rename-strip-prefix <stack>`) that
520
+ would let an existing stack adopt the new default without replacement
521
+ is tracked separately in [#300](https://github.com/go-to-k/cdkd/issues/300).
522
+
523
+ ### Effect on `cdkd export`
524
+
525
+ [PR #285 `cdkd export`](https://github.com/go-to-k/cdkd/pull/285)
526
+ surfaced the pre-v0.94.0 inconsistency: the CFn IMPORT changeset's
527
+ identifier check would fail on a synth `RoleName: 'my-role'` vs the
528
+ AWS-deployed `MyStack-my-role`, so the export command overlays
529
+ `ResourceIdentifier` onto `Properties` to bridge the gap. The
530
+ overlay is still needed for stacks deployed under the legacy default
531
+ (or with `--prefix-user-supplied-names`); a fresh stack deployed
532
+ under the v0.94.0 default has matching names and the overlay is a
533
+ no-op for it.
496
534
 
497
535
  ## `--remove-protection`: one-shot bypass for protected resources
498
536
 
package/dist/cli.js CHANGED
@@ -23396,9 +23396,20 @@ var deployOptions = [
23396
23396
  "Skip capturing AWS-current properties after each create/update (adds a fire-and-forget readCurrentState per resource so cdkd drift can compare against the real deploy-time AWS snapshot instead of the template). On by default. Disable when deploy speed matters more than rich drift detection \u2014 falls back to comparing against template properties (the pre-v3 behavior)."
23397
23397
  ),
23398
23398
  new Option(
23399
- "--no-prefix-user-supplied-names",
23400
- 'Do NOT prepend the stack name to physical names the user explicitly supplied in their CDK code (e.g. `new iam.Role(this, "X", { roleName: "my-role" })` \u2192 AWS resource named `my-role` instead of `MyStack-my-role`). Auto-generated-name resources (where the user did not declare a physical name) keep the prefix unchanged. Off by default for backward compatibility; enable via this flag, CDKD_NO_PREFIX_USER_SUPPLIED_NAMES=true, or cdk.json context.cdkd.noPrefixUserSuppliedNames=true. Applies to `cdkd deploy` only.'
23401
- ),
23399
+ "--prefix-user-supplied-names",
23400
+ 'Opt in to LEGACY behavior: prepend the stack name to physical names the user explicitly supplied in their CDK code (e.g. `new iam.Role(this, "X", { roleName: "my-role" })` \u2192 AWS resource named `MyStack-my-role` instead of `my-role`). Since v0.94.0 the default is to NOT prefix user-supplied names \u2014 this flag restores the pre-v0.94.0 behavior on Pattern B providers (IAM Role / User / Group / InstanceProfile / ELBv2 LoadBalancer / TargetGroup). Enable via this flag, CDKD_PREFIX_USER_SUPPLIED_NAMES=true, or cdk.json context.cdkd.prefixUserSuppliedNames=true. Applies to `cdkd deploy` only.'
23401
+ ).default(false),
23402
+ // Note: the deprecated `--no-prefix-user-supplied-names` flag is NOT
23403
+ // declared as a separate Option. Commander's automatic `--no-X`
23404
+ // negation lets users still pass it without error — it negates the
23405
+ // new `--prefix-user-supplied-names` flag, leaving its default
23406
+ // `false` (= skip prefix) unchanged, which matches the v0.94.0
23407
+ // default. Detection of the literal `--no-prefix-user-supplied-names`
23408
+ // flag for the deprecation warning happens via the pre-parse argv
23409
+ // walk in `warnDeprecatedNoPrefixCliFlag` (src/cli/config-loader.ts) —
23410
+ // declaring both Options together would have collapsed both onto a
23411
+ // single Commander key, making `noPrefixUserSuppliedNames` permanently
23412
+ // `undefined` and silencing the warning.
23402
23413
  noWaitOption,
23403
23414
  aggressiveVpcParallelOption,
23404
23415
  new Option(
@@ -23543,18 +23554,44 @@ function resolveCaptureObservedState(cliValue) {
23543
23554
  return v;
23544
23555
  return true;
23545
23556
  }
23546
- function resolveSkipPrefix(cliValue) {
23547
- if (cliValue === false)
23548
- return true;
23549
- const envValue = process.env["CDKD_NO_PREFIX_USER_SUPPLIED_NAMES"];
23550
- if (envValue === "true")
23551
- return true;
23557
+ function warnDeprecatedNoPrefixCliFlag(argv = process.argv) {
23558
+ const seen = argv.some(
23559
+ (a) => a === "--no-prefix-user-supplied-names" || a.startsWith("--no-prefix-user-supplied-names=")
23560
+ );
23561
+ if (seen) {
23562
+ getLogger().warn(
23563
+ "--no-prefix-user-supplied-names is deprecated since v0.94.0 \u2014 skipping the prefix is now the default. Remove the flag."
23564
+ );
23565
+ }
23566
+ }
23567
+ function resolveSkipPrefix(opts = {}) {
23568
+ const logger = getLogger();
23569
+ if (opts.prefixUserSuppliedNames === true) {
23570
+ return false;
23571
+ }
23572
+ const envPrefix = process.env["CDKD_PREFIX_USER_SUPPLIED_NAMES"];
23573
+ if (envPrefix === "true") {
23574
+ return false;
23575
+ }
23552
23576
  const cdkJson = loadCdkJson();
23553
23577
  const cdkdContext = cdkJson?.context?.["cdkd"];
23554
- const v = cdkdContext?.["noPrefixUserSuppliedNames"];
23555
- if (typeof v === "boolean" && v === true)
23556
- return true;
23557
- return false;
23578
+ const v = cdkdContext?.["prefixUserSuppliedNames"];
23579
+ if (typeof v === "boolean" && v === true) {
23580
+ return false;
23581
+ }
23582
+ const deprecatedEnv = process.env["CDKD_NO_PREFIX_USER_SUPPLIED_NAMES"];
23583
+ if (deprecatedEnv === "true") {
23584
+ logger.warn(
23585
+ "CDKD_NO_PREFIX_USER_SUPPLIED_NAMES is deprecated since v0.94.0 \u2014 skipping the prefix is now the default. Unset the env var."
23586
+ );
23587
+ }
23588
+ const deprecatedCdkJson = cdkdContext?.["noPrefixUserSuppliedNames"];
23589
+ if (typeof deprecatedCdkJson === "boolean" && deprecatedCdkJson === true) {
23590
+ logger.warn(
23591
+ "cdk.json context.cdkd.noPrefixUserSuppliedNames is deprecated since v0.94.0 \u2014 skipping the prefix is now the default. Remove the entry."
23592
+ );
23593
+ }
23594
+ return true;
23558
23595
  }
23559
23596
  function resolveStateBucketWithSource(cliBucket) {
23560
23597
  if (cliBucket)
@@ -65557,10 +65594,17 @@ async function deployCommand(stacks, options) {
65557
65594
  if (!options.wait) {
65558
65595
  process.env["CDKD_NO_WAIT"] = "true";
65559
65596
  }
65560
- const skipPrefix = resolveSkipPrefix(options.prefixUserSuppliedNames);
65597
+ warnDeprecatedNoPrefixCliFlag();
65598
+ const skipPrefix = resolveSkipPrefix({
65599
+ prefixUserSuppliedNames: options.prefixUserSuppliedNames
65600
+ });
65561
65601
  if (skipPrefix) {
65562
65602
  logger.debug(
65563
- "Skipping stack-name prefix on user-supplied physical names (--no-prefix-user-supplied-names / CDKD_NO_PREFIX_USER_SUPPLIED_NAMES / cdk.json context.cdkd.noPrefixUserSuppliedNames)"
65603
+ "Skipping stack-name prefix on user-supplied physical names (default since v0.94.0)"
65604
+ );
65605
+ } else {
65606
+ logger.debug(
65607
+ "Keeping legacy stack-name prefix on user-supplied physical names (--prefix-user-supplied-names / CDKD_PREFIX_USER_SUPPLIED_NAMES / cdk.json context.cdkd.prefixUserSuppliedNames)"
65564
65608
  );
65565
65609
  }
65566
65610
  const app = resolveApp(options.app);
@@ -79985,7 +80029,8 @@ var PRIMARY_IDENTIFIER_FALLBACK = {
79985
80029
  };
79986
80030
  var COMPOSITE_ID_SPLITTERS = {
79987
80031
  // cdkd stores `restApiId|resourceId|httpMethod` (apigateway-provider.ts);
79988
- // CFn primary identifier is [RestApiId, ResourceId, HttpMethod] — same order.
80032
+ // CFn primary identifier is [RestApiId, ResourceId, HttpMethod] — same
80033
+ // order, and all three are writable Properties of AWS::ApiGateway::Method.
79989
80034
  "AWS::ApiGateway::Method": (id) => {
79990
80035
  const parts = id.split("|");
79991
80036
  if (parts.length !== 3) {
@@ -79993,28 +80038,98 @@ var COMPOSITE_ID_SPLITTERS = {
79993
80038
  `expected 3 parts (restApiId|resourceId|httpMethod), got ${parts.length}: '${id}'`
79994
80039
  );
79995
80040
  }
79996
- return { RestApiId: parts[0], ResourceId: parts[1], HttpMethod: parts[2] };
80041
+ const map = { RestApiId: parts[0], ResourceId: parts[1], HttpMethod: parts[2] };
80042
+ return { resourceIdentifier: map };
79997
80043
  },
79998
80044
  // cdkd stores `restApiId|resourceId` (apigateway-provider.ts);
79999
- // CFn primary identifier is [RestApiId, ResourceId].
80045
+ // CFn primary identifier is [RestApiId, ResourceId] — both are writable
80046
+ // Properties of AWS::ApiGateway::Resource.
80000
80047
  "AWS::ApiGateway::Resource": (id) => {
80001
80048
  const parts = id.split("|");
80002
80049
  if (parts.length !== 2) {
80003
80050
  throw new Error(`expected 2 parts (restApiId|resourceId), got ${parts.length}: '${id}'`);
80004
80051
  }
80005
- return { RestApiId: parts[0], ResourceId: parts[1] };
80052
+ const map = { RestApiId: parts[0], ResourceId: parts[1] };
80053
+ return { resourceIdentifier: map };
80006
80054
  },
80007
80055
  // cdkd stores `IGW|VpcId` (ec2-provider.ts);
80008
80056
  // CFn primary identifier is [VpcId, InternetGatewayId] — DIFFERENT order
80009
- // from cdkd. Splitter reorders explicitly.
80057
+ // from cdkd. Splitter reorders explicitly. Both are writable Properties.
80010
80058
  "AWS::EC2::VPCGatewayAttachment": (id) => {
80011
80059
  const parts = id.split("|");
80012
80060
  if (parts.length !== 2) {
80013
80061
  throw new Error(`expected 2 parts (IGW|VpcId), got ${parts.length}: '${id}'`);
80014
80062
  }
80015
- return { VpcId: parts[1], InternetGatewayId: parts[0] };
80063
+ const map = { VpcId: parts[1], InternetGatewayId: parts[0] };
80064
+ return { resourceIdentifier: map };
80065
+ },
80066
+ // cdkd stores just `IntegrationId` (apigatewayv2-provider.ts); the parent
80067
+ // `ApiId` lives in cdkd state's properties (`properties.ApiId`). CFn primary
80068
+ // identifier is [ApiId, IntegrationId]. ApiId IS a writable Property
80069
+ // (already in synth template via Ref); IntegrationId is tagged
80070
+ // `readOnlyProperties: ['/properties/IntegrationId']` in the CFn schema —
80071
+ // exclude it from propertiesOverlay so CFn doesn't reject writing a
80072
+ // read-only property at changeset-create.
80073
+ "AWS::ApiGatewayV2::Integration": (physicalId, properties) => {
80074
+ const apiId = readStringProperty(properties, "ApiId", "AWS::ApiGatewayV2::Integration");
80075
+ return {
80076
+ resourceIdentifier: { ApiId: apiId, IntegrationId: physicalId },
80077
+ propertiesOverlay: { ApiId: apiId }
80078
+ };
80079
+ },
80080
+ // cdkd stores just `RouteId` (apigatewayv2-provider.ts); parent `ApiId`
80081
+ // comes from properties. CFn primary identifier is [ApiId, RouteId]. Same
80082
+ // overlay narrowing as Integration above.
80083
+ "AWS::ApiGatewayV2::Route": (physicalId, properties) => {
80084
+ const apiId = readStringProperty(properties, "ApiId", "AWS::ApiGatewayV2::Route");
80085
+ return {
80086
+ resourceIdentifier: { ApiId: apiId, RouteId: physicalId },
80087
+ propertiesOverlay: { ApiId: apiId }
80088
+ };
80089
+ },
80090
+ // NOTE: `AWS::ApiGatewayV2::Stage` is intentionally NOT in this map.
80091
+ // (1) AWS reports its primaryIdentifier as `['/properties/Id']` (single-key),
80092
+ // so cdkd's single-key resolution path handles it without a splitter.
80093
+ // (2) But AWS CloudFormation does NOT support `AWS::ApiGatewayV2::Stage` in
80094
+ // IMPORT changesets (CreateChangeSet rejects with "ResourceTypes
80095
+ // [AWS::ApiGatewayV2::Stage] are not supported for Import"). This means
80096
+ // `cdkd export` cannot complete on any stack that includes an HttpApi
80097
+ // (CDK auto-creates a `$default` Stage). Tracked in a follow-up issue
80098
+ // (link in PR description); the workaround design is open
80099
+ // (pre-delete + phase-2-CREATE vs hard-block-with-clear-error).
80100
+ // cdkd stores `StatementId` (lambda-permission-provider.ts:124); for state
80101
+ // entries written by the older CC-API path (pre-SDK-provider), physicalId
80102
+ // may instead be the legacy `<functionArn>|<statementId>` shape — the
80103
+ // provider's own delete / update / getAttribute paths normalize via
80104
+ // `physicalId.split('|').pop()` (see lambda-permission-provider.ts:160 /
80105
+ // 222 / 290). Mirror that here so legacy state still produces the
80106
+ // correct CFn Id field; otherwise CFn IMPORT's identifier-match would
80107
+ // compare `Id: '<arn>|<sid>'` against the AWS-current Sid and reject.
80108
+ //
80109
+ // CFn primary identifier is [FunctionName, Id] (note: CFn schema calls
80110
+ // the field `Id`, not `StatementId`). FunctionName IS a writable
80111
+ // Property; `Id` is tagged `readOnlyProperties: ['/properties/Id']` in
80112
+ // the CFn schema (it's set at create time by AWS, not by the user).
80113
+ // Narrow overlay to FunctionName so CFn doesn't reject writing read-only
80114
+ // `Id` at changeset-create.
80115
+ "AWS::Lambda::Permission": (physicalId, properties) => {
80116
+ const functionName = readStringProperty(properties, "FunctionName", "AWS::Lambda::Permission");
80117
+ const statementId = physicalId.includes("|") ? physicalId.split("|").pop() : physicalId;
80118
+ return {
80119
+ resourceIdentifier: { FunctionName: functionName, Id: statementId },
80120
+ propertiesOverlay: { FunctionName: functionName }
80121
+ };
80016
80122
  }
80017
80123
  };
80124
+ function readStringProperty(properties, key, resourceType) {
80125
+ const v = properties[key];
80126
+ if (typeof v !== "string" || !v) {
80127
+ throw new Error(
80128
+ `cdkd state's properties for ${resourceType} is missing '${key}' (the parent identifier required to build the CFn ResourceIdentifier map). State entry may be corrupt or written by an older cdkd binary; re-deploy the resource to refresh state.`
80129
+ );
80130
+ }
80131
+ return v;
80132
+ }
80018
80133
  async function exportCommand(stackArg, options) {
80019
80134
  const logger = getLogger();
80020
80135
  if (options.verbose) {
@@ -80392,11 +80507,12 @@ async function buildImportPlan(state, template, cfnClient) {
80392
80507
  });
80393
80508
  continue;
80394
80509
  }
80395
- let resourceIdentifier;
80510
+ let resolved;
80396
80511
  try {
80397
- resourceIdentifier = await resolveResourceIdentifier(
80512
+ resolved = await resolveResourceIdentifier(
80398
80513
  resourceType,
80399
80514
  stateEntry.physicalId,
80515
+ stateEntry.properties ?? {},
80400
80516
  cfnClient,
80401
80517
  identifierCache
80402
80518
  );
@@ -80412,19 +80528,21 @@ async function buildImportPlan(state, template, cfnClient) {
80412
80528
  logicalId,
80413
80529
  resourceType,
80414
80530
  physicalId: stateEntry.physicalId,
80415
- resourceIdentifier
80531
+ resourceIdentifier: resolved.resourceIdentifier,
80532
+ propertiesOverlay: resolved.propertiesOverlay ?? resolved.resourceIdentifier
80416
80533
  });
80417
80534
  }
80418
80535
  return { phase1Imports, phase2Creates, blocked };
80419
80536
  }
80420
- async function resolveResourceIdentifier(resourceType, physicalId, cfnClient, cache2) {
80537
+ async function resolveResourceIdentifier(resourceType, physicalId, properties, cfnClient, cache2) {
80421
80538
  let entry = cache2.get(resourceType);
80422
80539
  if (entry === void 0) {
80423
80540
  entry = await fetchPrimaryIdentifier(resourceType, cfnClient);
80424
80541
  cache2.set(resourceType, entry);
80425
80542
  }
80426
80543
  if (entry.fields.length === 1) {
80427
- return { [entry.fields[0]]: physicalId };
80544
+ const map = { [entry.fields[0]]: physicalId };
80545
+ return { resourceIdentifier: map };
80428
80546
  }
80429
80547
  const splitter = COMPOSITE_ID_SPLITTERS[resourceType];
80430
80548
  if (!splitter) {
@@ -80432,22 +80550,22 @@ async function resolveResourceIdentifier(resourceType, physicalId, cfnClient, ca
80432
80550
  `resource type uses a composite primary identifier (${entry.fields.length} fields: ${entry.fields.join(", ")}); add an entry to COMPOSITE_ID_SPLITTERS in src/cli/commands/export.ts that parses cdkd's physicalId for this type, or destroy the resource first and let CFn create it fresh`
80433
80551
  );
80434
80552
  }
80435
- let split;
80553
+ let result;
80436
80554
  try {
80437
- split = splitter(physicalId);
80555
+ result = splitter(physicalId, properties);
80438
80556
  } catch (err) {
80439
80557
  throw new Error(
80440
80558
  `composite-id splitter for ${resourceType} failed: ` + (err instanceof Error ? err.message : String(err))
80441
80559
  );
80442
80560
  }
80443
80561
  for (const f of entry.fields) {
80444
- if (!(f in split)) {
80562
+ if (!(f in result.resourceIdentifier)) {
80445
80563
  throw new Error(
80446
- `composite-id splitter for ${resourceType} did not produce field '${f}' (produced: ${Object.keys(split).join(", ")})`
80564
+ `composite-id splitter for ${resourceType} did not produce field '${f}' (produced: ${Object.keys(result.resourceIdentifier).join(", ")})`
80447
80565
  );
80448
80566
  }
80449
80567
  }
80450
- return split;
80568
+ return result;
80451
80569
  }
80452
80570
  async function fetchPrimaryIdentifier(resourceType, cfnClient) {
80453
80571
  try {
@@ -80570,7 +80688,8 @@ function overlayResourceIdentifierOnProperties(resource, entry) {
80570
80688
  const r = resource;
80571
80689
  const existingProperties = r["Properties"];
80572
80690
  const properties = existingProperties && typeof existingProperties === "object" && !Array.isArray(existingProperties) ? { ...existingProperties } : {};
80573
- for (const [field, value] of Object.entries(entry.resourceIdentifier)) {
80691
+ const overlay = entry.propertiesOverlay ?? entry.resourceIdentifier;
80692
+ for (const [field, value] of Object.entries(overlay)) {
80574
80693
  properties[field] = value;
80575
80694
  }
80576
80695
  return { ...r, Properties: properties };
@@ -80938,7 +81057,7 @@ function reorderArgs(argv) {
80938
81057
  }
80939
81058
  async function main() {
80940
81059
  const program = new Command18();
80941
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.93.0");
81060
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.94.1");
80942
81061
  program.addCommand(createBootstrapCommand());
80943
81062
  program.addCommand(createSynthCommand());
80944
81063
  program.addCommand(createListCommand());