@go-to-k/cdkd 0.246.0 → 0.248.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/README.md CHANGED
@@ -4,15 +4,14 @@
4
4
  [![Downloads](https://img.shields.io/npm/dw/@go-to-k/cdkd.svg)](https://www.npmjs.com/package/@go-to-k/cdkd)
5
5
  [![License: Apache-2.0](https://img.shields.io/npm/l/@go-to-k/cdkd.svg)](./LICENSE)
6
6
 
7
- Drop-in CDK CLI for existing CDK apps — faster deploys via AWS SDK instead of CloudFormation, plus local emulation for Lambda, API Gateway, and ECS.
7
+ Drop-in CDK CLI for existing CDK apps — up to 15x faster deploys via direct AWS SDK calls instead of CloudFormation.
8
8
 
9
- - **Drop-in CDK compatible** your existing CDK app code runs as-is.
10
- - **Up to 15x faster deploys than the AWS CDK CLI (CloudFormation)**
11
- - **Local dev for any CDK app** — invoke Lambdas, serve API Gateway routes, run ECS tasks/services directly from your CDK code. Works against both `cdkd deploy`-managed AND `cdk deploy`-managed (CloudFormation) stacks via `--from-state` / `--from-cfn-stack` — no migration, no `cdk synth → sam local` round-trip.
9
+ - **Drop-in CDK compatible**: your existing CDK app code runs as-is.
10
+ - **Up to 15x faster deploys**: direct SDK calls, aggressive parallelization, and `--no-wait` to skip slow stabilization waits.
12
11
 
13
12
  ![cdk deploy vs cdkd deploy — side-by-side, 35s recording, real AWS deploy. cdkd finishes while cdk is still creating its CloudFormation changeset.](assets/cdk-vs-cdkd.gif)
14
13
 
15
- **cdkd complements the AWS CDK CLI rather than replacing it.** Use cdkd in dev/test for rapid iteration and SAM-style local execution; use the AWS CDK CLI in production for full CloudFormation tooling. Install cdkd alongside an existing `cdk deploy` workflow no migration needed, `cdkd local *` reads deployed state directly via `--from-cfn-stack`. Bidirectional migration is also supported [import](#importing-existing-resources) into cdkd or [export](#exporting-a-stack-back-to-cloudformation) back to CloudFormation when ready.
14
+ **cdkd complements the AWS CDK CLI rather than replacing it.** Use cdkd in dev/test for rapid iteration and local execution; use the AWS CDK CLI in production for full CloudFormation tooling. Install cdkd alongside an existing `cdk deploy` workflow: no migration needed. Bidirectional migration is also supported: [import](#importing-existing-resources) into cdkd or [export](#exporting-a-stack-back-to-cloudformation) back to CloudFormation when ready.
16
15
 
17
16
  **A natural fit for AI-driven development.** AI coding agents iterate in tight spin-up / tear-down loops — and cdkd keeps each turn short, with fast deploys and an equally fast `cdkd destroy` that deletes via direct SDK calls instead of polling a CloudFormation stack-delete.
18
17
 
@@ -139,13 +138,14 @@ parsing → synthesis → asset publishing → per-stack deploy), see
139
138
  ## Prerequisites
140
139
 
141
140
  - **Node.js** >= 20.0.0
142
- - **AWS CDK Bootstrap: not required.** `cdkd bootstrap` (once per account) creates
143
- everything cdkd needs, and per-region asset storage is added automatically on the
144
- first `cdkd deploy` into each region. Existing setups, legacy-mode opt-outs, and
145
- how this relates to `cdk bootstrap`: see
146
- [Upgrading from an earlier cdkd version](#upgrading-from-an-earlier-cdkd-version).
147
141
  - **AWS credentials with admin-equivalent permissions** for the resources being deployed. cdkd does NOT route through CloudFormation, so CDK CLI's `cdk-hnb659fds-deploy-role-*` is NOT sufficient — see [`--role-arn`](docs/cli-reference.md).
148
142
 
143
+ AWS CDK's `cdk bootstrap` is not required. Instead, run `cdkd bootstrap` once per
144
+ account: it creates everything cdkd needs, and per-region asset storage is added
145
+ automatically on the first `cdkd deploy` into each region. Existing setups,
146
+ legacy-mode opt-outs, and how this relates to `cdk bootstrap`: see
147
+ [Upgrading from an earlier cdkd version](#upgrading-from-an-earlier-cdkd-version).
148
+
149
149
  ## Installation
150
150
 
151
151
  ```bash
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BzO-joNR.js";
3
3
  import { A as withErrorHandling, C as StackHasActiveImportsError, S as ResourceUpdateNotSupportedError, _ as MissingCdkCliError, a as normalizeAwsTagsToCfn, b as ProvisioningError, f as LocalInvokeBuildError$1, i as matchesCdkPath, k as normalizeAwsError, l as CdkdError, m as LocalStartServiceError, n as isTerminationProtectionPropagationError, o as resolveExplicitPhysicalId, p as LocalMigrateError, r as CDK_PATH_TAG, s as assertRegionMatch, t as disableInstanceApiTermination, v as NestedStackChildDirectDestroyError, w as StackTerminationProtectionError, x as ResourceTimeoutError, y as PartialFailureError } from "./ec2-termination-protection-q-WGWgxa.js";
4
4
  import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-B15NAPbL.js";
5
- import { $ as getDockerCmd, A as DiffCalculator, B as buildAssetRedirectMap, C as findActionableSilentDrops, Ct as resolveBucketRegion, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as BOOTSTRAP_MARKER_PREFIX, H as loadPublishableAssetManifest, I as shouldRetainResource, J as parseBootstrapMarker, K as ensureAssetStorage, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as formatDockerLoginError, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as rewriteTemplateAssetReferences, V as createAssetRedirectResolver, W as AssetModeResolver, X as validateContainerRepoName, Y as validateAssetBucketName, Z as buildDockerImage, _ as green, _t as CFN_TEMPLATE_URL_LIMIT, a as withRetry, at as synthesisStatusMessage, b as IAMRoleProvider, bt as uploadCfnTemplate, c as computeImplicitDeleteEdges, ct as resolveApp, d as isStatefulRecreateTargetSync, dt as resolveSkipPrefix, et as runDockerForeground, f as renderStatefulReason, ft as resolveStateBucketWithDefault, g as gray, gt as CFN_TEMPLATE_BODY_LIMIT, h as cyan, ht as warnDeprecatedNoPrefixCliFlag, i as withResourceDeadline, it as Synthesizer, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, lt as resolveAutoAssetStorage, m as bold, mt as resolveUseCdkBootstrapAssets, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as AssetManifestLoader, o as isRetryableTransientError, ot as getDefaultStateBucketName, p as formatResourceLine, pt as resolveStateBucketWithDefaultAndSource, q as getBootstrapMarkerKey, r as DeployEngine, rt as getDockerImageBySourceHash, s as IMPLICIT_DELETE_DEPENDENCIES, st as getLegacyStateBucketName, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as runDockerStreaming, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveCaptureObservedState, v as red, vt as MIGRATE_TMP_PREFIX, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, xt as AssemblyReader, y as yellow, yt as findLargeInlineResources, z as WorkGraph } from "./deploy-engine-Ci_7Y2ol.js";
5
+ import { $ as getDockerCmd, A as DiffCalculator, B as buildAssetRedirectMap, C as findActionableSilentDrops, Ct as resolveBucketRegion, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as BOOTSTRAP_MARKER_PREFIX, H as loadPublishableAssetManifest, I as shouldRetainResource, J as parseBootstrapMarker, K as ensureAssetStorage, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as formatDockerLoginError, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as rewriteTemplateAssetReferences, V as createAssetRedirectResolver, W as AssetModeResolver, X as validateContainerRepoName, Y as validateAssetBucketName, Z as buildDockerImage, _ as green, _t as CFN_TEMPLATE_URL_LIMIT, a as withRetry, at as synthesisStatusMessage, b as IAMRoleProvider, bt as uploadCfnTemplate, c as computeImplicitDeleteEdges, ct as resolveApp, d as isStatefulRecreateTargetSync, dt as resolveSkipPrefix, et as runDockerForeground, f as renderStatefulReason, ft as resolveStateBucketWithDefault, g as gray, gt as CFN_TEMPLATE_BODY_LIMIT, h as cyan, ht as warnDeprecatedNoPrefixCliFlag, i as withResourceDeadline, it as Synthesizer, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, lt as resolveAutoAssetStorage, m as bold, mt as resolveUseCdkBootstrapAssets, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as AssetManifestLoader, o as isRetryableTransientError, ot as getDefaultStateBucketName, p as formatResourceLine, pt as resolveStateBucketWithDefaultAndSource, q as getBootstrapMarkerKey, r as DeployEngine, rt as getDockerImageBySourceHash, s as IMPLICIT_DELETE_DEPENDENCIES, st as getLegacyStateBucketName, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as runDockerStreaming, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveCaptureObservedState, v as red, vt as MIGRATE_TMP_PREFIX, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, xt as AssemblyReader, y as yellow, yt as findLargeInlineResources, z as WorkGraph } from "./deploy-engine-D6U-Cm8B.js";
6
6
  import { t as expectedOwnerParam } from "./expected-bucket-owner-BjEfUnkA.js";
7
7
  import { t as ASGProvider } from "./asg-provider-Dbl1_mwT.js";
8
8
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -60,11 +60,12 @@ import { AddTagsToStreamCommand, CreateStreamCommand, DecreaseStreamRetentionPer
60
60
  import { CreateScheduleCommand, DeleteScheduleCommand, GetScheduleCommand, ResourceNotFoundException as ResourceNotFoundException$9, SchedulerClient, UpdateScheduleCommand } from "@aws-sdk/client-scheduler";
61
61
  import { AccessPointNotFound, CreateAccessPointCommand, CreateFileSystemCommand, CreateMountTargetCommand, DeleteAccessPointCommand, DeleteFileSystemCommand, DeleteMountTargetCommand, DescribeAccessPointsCommand, DescribeBackupPolicyCommand, DescribeFileSystemPolicyCommand, DescribeFileSystemsCommand, DescribeLifecycleConfigurationCommand, DescribeMountTargetSecurityGroupsCommand, DescribeMountTargetsCommand, EFSClient, FileSystemNotFound, ModifyMountTargetSecurityGroupsCommand, MountTargetNotFound, PutBackupPolicyCommand, PutFileSystemPolicyCommand, PutLifecycleConfigurationCommand, UpdateFileSystemCommand, UpdateFileSystemProtectionCommand } from "@aws-sdk/client-efs";
62
62
  import { CreateFileSystemCommand as CreateFileSystemCommand$1, CreateFileSystemFromBackupCommand, DeleteFileSystemCommand as DeleteFileSystemCommand$1, DescribeFileSystemsCommand as DescribeFileSystemsCommand$1, FSxClient, FileSystemNotFound as FileSystemNotFound$1, TagResourceCommand as TagResourceCommand$18, UntagResourceCommand as UntagResourceCommand$18, UpdateFileSystemCommand as UpdateFileSystemCommand$1 } from "@aws-sdk/client-fsx";
63
- import { AddTagsCommand as AddTagsCommand$1, DescribeClusterCommand, EMRClient, InvalidRequestException, ModifyClusterCommand, PutAutoTerminationPolicyCommand, PutManagedScalingPolicyCommand, RemoveAutoTerminationPolicyCommand, RemoveManagedScalingPolicyCommand, RemoveTagsCommand as RemoveTagsCommand$1, RunJobFlowCommand, SetTerminationProtectionCommand, SetVisibleToAllUsersCommand, TerminateJobFlowsCommand } from "@aws-sdk/client-emr";
63
+ import { AddTagsCommand as AddTagsCommand$1, DescribeClusterCommand, EMRClient, InvalidRequestException, ListClustersCommand as ListClustersCommand$1, ListInstanceFleetsCommand, ListInstanceGroupsCommand, ModifyClusterCommand, PutAutoTerminationPolicyCommand, PutManagedScalingPolicyCommand, RemoveAutoTerminationPolicyCommand, RemoveManagedScalingPolicyCommand, RemoveTagsCommand as RemoveTagsCommand$1, RunJobFlowCommand, SetTerminationProtectionCommand, SetVisibleToAllUsersCommand, TerminateJobFlowsCommand } from "@aws-sdk/client-emr";
64
64
  import { CreateDeliveryStreamCommand, DeleteDeliveryStreamCommand, DescribeDeliveryStreamCommand, FirehoseClient, ListDeliveryStreamsCommand, ListTagsForDeliveryStreamCommand, ResourceNotFoundException as ResourceNotFoundException$10, TagDeliveryStreamCommand, UntagDeliveryStreamCommand, UpdateDestinationCommand } from "@aws-sdk/client-firehose";
65
65
  import { AddTagsCommand as AddTagsCommand$2, CloudTrailClient, CreateTrailCommand, DeleteTrailCommand, GetEventSelectorsCommand, GetInsightSelectorsCommand, GetTrailCommand, GetTrailStatusCommand, ListTagsCommand as ListTagsCommand$1, ListTrailsCommand, PutEventSelectorsCommand, PutInsightSelectorsCommand, RemoveTagsCommand as RemoveTagsCommand$2, StartLoggingCommand, StopLoggingCommand, TrailNotFoundException, UpdateTrailCommand } from "@aws-sdk/client-cloudtrail";
66
66
  import { BatchGetProjectsCommand, CodeBuildClient, CreateProjectCommand, DeleteProjectCommand, ListProjectsCommand, ResourceNotFoundException as ResourceNotFoundException$11, UpdateProjectCommand } from "@aws-sdk/client-codebuild";
67
- import { CodeCommitClient, CreateRepositoryCommand as CreateRepositoryCommand$1, DeleteRepositoryCommand as DeleteRepositoryCommand$1, GetRepositoryCommand, ListRepositoriesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$19, RepositoryDoesNotExistException, TagResourceCommand as TagResourceCommand$19, UntagResourceCommand as UntagResourceCommand$19, UpdateRepositoryDescriptionCommand, UpdateRepositoryEncryptionKeyCommand, UpdateRepositoryNameCommand } from "@aws-sdk/client-codecommit";
67
+ import { CodeCommitClient, CreateCommitCommand, CreateRepositoryCommand as CreateRepositoryCommand$1, DeleteRepositoryCommand as DeleteRepositoryCommand$1, GetRepositoryCommand, ListRepositoriesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$19, PutRepositoryTriggersCommand, RepositoryDoesNotExistException, TagResourceCommand as TagResourceCommand$19, UntagResourceCommand as UntagResourceCommand$19, UpdateRepositoryDescriptionCommand, UpdateRepositoryEncryptionKeyCommand, UpdateRepositoryNameCommand } from "@aws-sdk/client-codecommit";
68
+ import AdmZip from "adm-zip";
68
69
  import { CreateLifecyclePolicyCommand, DLMClient, DeleteLifecyclePolicyCommand as DeleteLifecyclePolicyCommand$1, GetLifecyclePoliciesCommand, GetLifecyclePolicyCommand as GetLifecyclePolicyCommand$1, ResourceNotFoundException as ResourceNotFoundException$12, TagResourceCommand as TagResourceCommand$20, UntagResourceCommand as UntagResourceCommand$20, UpdateLifecyclePolicyCommand } from "@aws-sdk/client-dlm";
69
70
  import { CreateVectorBucketCommand, DeleteIndexCommand, DeleteVectorBucketCommand, GetVectorBucketCommand, ListIndexesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$20, ListVectorBucketsCommand, S3VectorsClient, TagResourceCommand as TagResourceCommand$21, UntagResourceCommand as UntagResourceCommand$21 } from "@aws-sdk/client-s3vectors";
70
71
  import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as CreateTableCommand$2, DeleteNamespaceCommand as DeleteNamespaceCommand$1, DeleteTableBucketCommand, DeleteTableCommand as DeleteTableCommand$2, GetTableBucketCommand, GetTableCommand as GetTableCommand$1, ListNamespacesCommand as ListNamespacesCommand$1, ListTableBucketsCommand, ListTablesCommand as ListTablesCommand$1, ListTagsForResourceCommand as ListTagsForResourceCommand$21, NotFoundException as NotFoundException$6, S3TablesClient, TagResourceCommand as TagResourceCommand$22, UntagResourceCommand as UntagResourceCommand$22 } from "@aws-sdk/client-s3tables";
@@ -1890,7 +1891,7 @@ const FLUSH_INTERVAL_MS = 2e3;
1890
1891
  const FLUSH_EVENT_THRESHOLD = 50;
1891
1892
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
1892
1893
  function getCdkdVersion() {
1893
- return "0.246.0";
1894
+ return "0.248.0";
1894
1895
  }
1895
1896
  /**
1896
1897
  * Generate a time-sortable unique run id, e.g.
@@ -35020,6 +35021,18 @@ const CREATE_READY_STATES = new Set(["WAITING", "RUNNING"]);
35020
35021
  */
35021
35022
  const TERMINAL_STATES = new Set(["TERMINATED", "TERMINATED_WITH_ERRORS"]);
35022
35023
  /**
35024
+ * Cluster states that mean "the cluster is alive" — the `ListClusters` filter
35025
+ * used by `import()` discovery so a long-gone `TERMINATED*` cluster is never
35026
+ * adopted. Everything except the two `TERMINAL_STATES`.
35027
+ */
35028
+ const NON_TERMINATED_STATES = [
35029
+ "STARTING",
35030
+ "BOOTSTRAPPING",
35031
+ "RUNNING",
35032
+ "WAITING",
35033
+ "TERMINATING"
35034
+ ];
35035
+ /**
35023
35036
  * Top-level CFn properties that map to a MUTABLE EMR API surface. Every other
35024
35037
  * property is either registry-createOnly (routed through DELETE+CREATE by the
35025
35038
  * replacement-detection layer) or lives inside the `Instances` block (handled
@@ -35519,6 +35532,279 @@ var EMRClusterProvider = class {
35519
35532
  default: return;
35520
35533
  }
35521
35534
  }
35535
+ /**
35536
+ * Adopt an existing EMR cluster into cdkd state.
35537
+ *
35538
+ * Lookup order:
35539
+ * 1. `--resource <logicalId>=j-XXXX` override (`knownPhysicalId`) → verify
35540
+ * via `DescribeCluster`. There is no template name property that equals
35541
+ * the physical id — a cluster's id (`j-...`) is service-generated, while
35542
+ * the template `Name` is only the display name — so no name fallback
35543
+ * applies (`resolveExplicitPhysicalId(..., null)`).
35544
+ * 2. Tag-based lookup: `ListClusters` filtered to the non-terminated states
35545
+ * (a `TERMINATED*` cluster is gone and must never be adopted), then a
35546
+ * `DescribeCluster` per candidate to read `Tags` (the list summaries do
35547
+ * NOT carry tags) and match `aws:cdk:path`.
35548
+ */
35549
+ async import(input) {
35550
+ const explicit = resolveExplicitPhysicalId(input, null);
35551
+ if (explicit) {
35552
+ const cluster = await this.describeClusterOrNull(explicit);
35553
+ if (!cluster || cluster.Status?.State && TERMINAL_STATES.has(cluster.Status.State)) return null;
35554
+ return {
35555
+ physicalId: explicit,
35556
+ attributes: this.buildAttributes(cluster)
35557
+ };
35558
+ }
35559
+ if (!input.cdkPath) return null;
35560
+ let marker;
35561
+ do {
35562
+ const list = await this.getClient().send(new ListClustersCommand$1({
35563
+ ClusterStates: [...NON_TERMINATED_STATES],
35564
+ ...marker && { Marker: marker }
35565
+ }));
35566
+ for (const summary of list.Clusters ?? []) {
35567
+ if (!summary.Id) continue;
35568
+ const cluster = await this.describeClusterOrNull(summary.Id);
35569
+ if (!cluster) continue;
35570
+ if (matchesCdkPath(cluster.Tags, input.cdkPath)) return {
35571
+ physicalId: summary.Id,
35572
+ attributes: this.buildAttributes(cluster)
35573
+ };
35574
+ }
35575
+ marker = list.Marker;
35576
+ } while (marker);
35577
+ return null;
35578
+ }
35579
+ /**
35580
+ * `DescribeCluster` that maps a not-found (`InvalidRequestException` — the
35581
+ * cluster id is unknown in this region / aged out of Describe) to `null`
35582
+ * instead of throwing, so `import()` can treat it as "no match" rather than
35583
+ * aborting the whole adoption run.
35584
+ */
35585
+ async describeClusterOrNull(clusterId) {
35586
+ try {
35587
+ return (await this.getClient().send(new DescribeClusterCommand({ ClusterId: clusterId }))).Cluster;
35588
+ } catch (err) {
35589
+ if (err instanceof InvalidRequestException) return void 0;
35590
+ throw err;
35591
+ }
35592
+ }
35593
+ /**
35594
+ * Read the currently-deployed properties for `cdkd drift` and to seed the
35595
+ * `observedProperties` baseline right after `cdkd import`.
35596
+ *
35597
+ * The bulk of the work is reversing the flatten that `create()` applies to
35598
+ * the CFn `Instances` block: `DescribeCluster` reports the cluster's
35599
+ * `InstanceCollectionType`, and the instance groups / fleets themselves come
35600
+ * from `ListInstanceGroups` / `ListInstanceFleets` as FLAT arrays with a
35601
+ * per-entry `InstanceGroupType` / `InstanceFleetType` discriminator — the
35602
+ * exact inverse of the role-keyed CFn `MasterInstanceGroup` /
35603
+ * `CoreInstanceGroup` / `TaskInstanceGroups` (+ `*InstanceFleet(s)`) shape.
35604
+ * `reverseInstancesToCfn` re-buckets them and folds in the flat
35605
+ * `Ec2InstanceAttributes` (subnet / key name / security groups).
35606
+ *
35607
+ * Scope: the reversible property set — the reverse-mapped `Instances` block,
35608
+ * `Tags` (via `normalizeAwsTagsToCfn`, `aws:*` stripped), and the top-level
35609
+ * scalar fields `DescribeCluster` returns directly. Create-only sub-config
35610
+ * that AWS does not read back faithfully (`ManagedScalingPolicy` /
35611
+ * `AutoTerminationPolicy` — separate Get* APIs; `BootstrapActions` / `Steps`
35612
+ * / `KerberosAttributes` / `Configurations` nesting / `AdditionalInfo` /
35613
+ * `PlacementGroupConfigs`; `Instances` EBS / auto-scaling sub-specs) is
35614
+ * declared in `getDriftUnknownPaths` so the drift comparator skips it on the
35615
+ * `properties`-fallback path instead of firing a guaranteed false positive.
35616
+ * On the normal path the baseline is `observedProperties` (captured via this
35617
+ * same method), so observed == current and there is no phantom drift.
35618
+ *
35619
+ * Returns `undefined` when the cluster is gone (`InvalidRequestException`)
35620
+ * so the caller reports drift-unknown rather than throwing — mirrors the
35621
+ * optional `import` method's incremental opt-in shape.
35622
+ */
35623
+ async readCurrentState(physicalId, _logicalId, _resourceType) {
35624
+ const cluster = await this.describeClusterOrNull(physicalId);
35625
+ if (!cluster) return void 0;
35626
+ let instanceGroups = [];
35627
+ let instanceFleets = [];
35628
+ if (cluster.InstanceCollectionType === "INSTANCE_FLEET") instanceFleets = await this.listInstanceFleets(physicalId);
35629
+ else instanceGroups = await this.listInstanceGroups(physicalId);
35630
+ return {
35631
+ Name: cluster.Name ?? "",
35632
+ ReleaseLabel: cluster.ReleaseLabel ?? "",
35633
+ ServiceRole: cluster.ServiceRole ?? "",
35634
+ LogUri: cluster.LogUri ?? "",
35635
+ LogEncryptionKmsKeyId: cluster.LogEncryptionKmsKeyId ?? "",
35636
+ AutoScalingRole: cluster.AutoScalingRole ?? "",
35637
+ ScaleDownBehavior: cluster.ScaleDownBehavior ?? "",
35638
+ CustomAmiId: cluster.CustomAmiId ?? "",
35639
+ OSReleaseLabel: cluster.OSReleaseLabel ?? "",
35640
+ SecurityConfiguration: cluster.SecurityConfiguration ?? "",
35641
+ EbsRootVolumeSize: cluster.EbsRootVolumeSize,
35642
+ EbsRootVolumeIops: cluster.EbsRootVolumeIops,
35643
+ EbsRootVolumeThroughput: cluster.EbsRootVolumeThroughput,
35644
+ StepConcurrencyLevel: cluster.StepConcurrencyLevel,
35645
+ VisibleToAllUsers: cluster.VisibleToAllUsers ?? false,
35646
+ JobFlowRole: cluster.Ec2InstanceAttributes?.IamInstanceProfile ?? "",
35647
+ Applications: cluster.Applications ?? [],
35648
+ Tags: normalizeAwsTagsToCfn(cluster.Tags),
35649
+ Instances: this.reverseInstancesToCfn(cluster, instanceGroups, instanceFleets)
35650
+ };
35651
+ }
35652
+ /**
35653
+ * State property paths this provider cannot read back from AWS faithfully,
35654
+ * skipped by the drift comparator to avoid a guaranteed false positive on
35655
+ * the `properties`-fallback path. See `readCurrentState`'s docstring.
35656
+ *
35657
+ * NOTE on the `Instances.*` entries: `reverseInstancesToCfn` deliberately
35658
+ * reconstructs only the primary topology per group / fleet (role, type,
35659
+ * count / capacity, market, bid price, name, custom AMI) — the lossy
35660
+ * sub-specs `create()` reads (`EbsConfiguration` / `AutoScalingPolicy` /
35661
+ * per-group `Configurations`; per-fleet `InstanceTypeConfigs` /
35662
+ * `LaunchSpecifications` / `ResizeSpecifications`) and the top-level
35663
+ * `Instances` fields AWS does not report back (`HadoopVersion` /
35664
+ * `Placement` / `KeepJobFlowAliveWhenNoSteps`) are NOT reconstructed. On the
35665
+ * NORMAL drift path both the `observedProperties` baseline and the current
35666
+ * snapshot go through this same lossy reverse, so these paths are absent
35667
+ * from both sides and never drift — ignoring them there is a no-op. On the
35668
+ * `properties`-fallback path (no `observedProperties`) the baseline is the
35669
+ * full template `Instances`, which DOES carry these sub-fields, so without
35670
+ * the skip every one would fire guaranteed false-positive drift. The two
35671
+ * `Task*` arrays are ignored WHOLE (the comparator compares arrays as leaves,
35672
+ * so an element sub-field cannot be path-targeted); Master/Core groups and
35673
+ * fleets are single objects, so their lossy sub-fields are targeted directly.
35674
+ */
35675
+ getDriftUnknownPaths(_resourceType) {
35676
+ return [
35677
+ "AdditionalInfo",
35678
+ "Applications",
35679
+ "AutoTerminationPolicy",
35680
+ "BootstrapActions",
35681
+ "Configurations",
35682
+ "Instances.HadoopVersion",
35683
+ "Instances.KeepJobFlowAliveWhenNoSteps",
35684
+ "Instances.Placement",
35685
+ "Instances.CoreInstanceGroup.AutoScalingPolicy",
35686
+ "Instances.CoreInstanceGroup.Configurations",
35687
+ "Instances.CoreInstanceGroup.EbsConfiguration",
35688
+ "Instances.MasterInstanceGroup.AutoScalingPolicy",
35689
+ "Instances.MasterInstanceGroup.Configurations",
35690
+ "Instances.MasterInstanceGroup.EbsConfiguration",
35691
+ "Instances.CoreInstanceFleet.InstanceTypeConfigs",
35692
+ "Instances.CoreInstanceFleet.LaunchSpecifications",
35693
+ "Instances.CoreInstanceFleet.ResizeSpecifications",
35694
+ "Instances.MasterInstanceFleet.InstanceTypeConfigs",
35695
+ "Instances.MasterInstanceFleet.LaunchSpecifications",
35696
+ "Instances.MasterInstanceFleet.ResizeSpecifications",
35697
+ "Instances.TaskInstanceFleets",
35698
+ "Instances.TaskInstanceGroups",
35699
+ "KerberosAttributes",
35700
+ "ManagedScalingPolicy",
35701
+ "PlacementGroupConfigs",
35702
+ "Steps"
35703
+ ];
35704
+ }
35705
+ async listInstanceGroups(clusterId) {
35706
+ const groups = [];
35707
+ let marker;
35708
+ do {
35709
+ const resp = await this.getClient().send(new ListInstanceGroupsCommand({
35710
+ ClusterId: clusterId,
35711
+ ...marker && { Marker: marker }
35712
+ }));
35713
+ groups.push(...resp.InstanceGroups ?? []);
35714
+ marker = resp.Marker;
35715
+ } while (marker);
35716
+ return groups;
35717
+ }
35718
+ async listInstanceFleets(clusterId) {
35719
+ const fleets = [];
35720
+ let marker;
35721
+ do {
35722
+ const resp = await this.getClient().send(new ListInstanceFleetsCommand({
35723
+ ClusterId: clusterId,
35724
+ ...marker && { Marker: marker }
35725
+ }));
35726
+ fleets.push(...resp.InstanceFleets ?? []);
35727
+ marker = resp.Marker;
35728
+ } while (marker);
35729
+ return fleets;
35730
+ }
35731
+ /**
35732
+ * Reverse of `toJobFlowInstancesConfig` — re-bucket the flat SDK
35733
+ * `InstanceGroup[]` / `InstanceFleet[]` (each carrying a role discriminator)
35734
+ * back into the role-keyed CFn `Instances` shape, and fold in the flat
35735
+ * `Ec2InstanceAttributes` (subnet / key name / security groups).
35736
+ *
35737
+ * Only the primary topology fields per group / fleet are reversed (role,
35738
+ * type, count / capacity, market, bid price, name, custom AMI). Lossy
35739
+ * sub-specs (EBS block-device / auto-scaling for groups; per-instance-type
35740
+ * specs for fleets) are intentionally NOT reconstructed — they are covered
35741
+ * by the drift baseline being `observedProperties` on the normal path.
35742
+ */
35743
+ reverseInstancesToCfn(cluster, instanceGroups, instanceFleets) {
35744
+ const ec2 = cluster.Ec2InstanceAttributes;
35745
+ const instances = {};
35746
+ const taskGroups = [];
35747
+ for (const g of instanceGroups) {
35748
+ const cfn = this.reverseInstanceGroup(g);
35749
+ switch (g.InstanceGroupType) {
35750
+ case "MASTER":
35751
+ instances["MasterInstanceGroup"] = cfn;
35752
+ break;
35753
+ case "CORE":
35754
+ instances["CoreInstanceGroup"] = cfn;
35755
+ break;
35756
+ case "TASK":
35757
+ taskGroups.push(cfn);
35758
+ break;
35759
+ }
35760
+ }
35761
+ if (taskGroups.length > 0) instances["TaskInstanceGroups"] = taskGroups;
35762
+ const taskFleets = [];
35763
+ for (const f of instanceFleets) {
35764
+ const cfn = this.reverseInstanceFleet(f);
35765
+ switch (f.InstanceFleetType) {
35766
+ case "MASTER":
35767
+ instances["MasterInstanceFleet"] = cfn;
35768
+ break;
35769
+ case "CORE":
35770
+ instances["CoreInstanceFleet"] = cfn;
35771
+ break;
35772
+ case "TASK":
35773
+ taskFleets.push(cfn);
35774
+ break;
35775
+ }
35776
+ }
35777
+ if (taskFleets.length > 0) instances["TaskInstanceFleets"] = taskFleets;
35778
+ if (ec2?.Ec2KeyName !== void 0) instances["Ec2KeyName"] = ec2.Ec2KeyName;
35779
+ if (ec2?.Ec2SubnetId !== void 0) instances["Ec2SubnetId"] = ec2.Ec2SubnetId;
35780
+ if (ec2?.RequestedEc2SubnetIds !== void 0) instances["Ec2SubnetIds"] = ec2.RequestedEc2SubnetIds;
35781
+ if (ec2?.EmrManagedMasterSecurityGroup !== void 0) instances["EmrManagedMasterSecurityGroup"] = ec2.EmrManagedMasterSecurityGroup;
35782
+ if (ec2?.EmrManagedSlaveSecurityGroup !== void 0) instances["EmrManagedSlaveSecurityGroup"] = ec2.EmrManagedSlaveSecurityGroup;
35783
+ if (ec2?.ServiceAccessSecurityGroup !== void 0) instances["ServiceAccessSecurityGroup"] = ec2.ServiceAccessSecurityGroup;
35784
+ if (ec2?.AdditionalMasterSecurityGroups !== void 0) instances["AdditionalMasterSecurityGroups"] = ec2.AdditionalMasterSecurityGroups;
35785
+ if (ec2?.AdditionalSlaveSecurityGroups !== void 0) instances["AdditionalSlaveSecurityGroups"] = ec2.AdditionalSlaveSecurityGroups;
35786
+ if (cluster.TerminationProtected !== void 0) instances["TerminationProtected"] = cluster.TerminationProtected;
35787
+ if (cluster.UnhealthyNodeReplacement !== void 0) instances["UnhealthyNodeReplacement"] = cluster.UnhealthyNodeReplacement;
35788
+ return instances;
35789
+ }
35790
+ reverseInstanceGroup(g) {
35791
+ const out = {
35792
+ InstanceType: g.InstanceType ?? "",
35793
+ InstanceCount: g.RequestedInstanceCount ?? 0
35794
+ };
35795
+ if (g.Name !== void 0) out["Name"] = g.Name;
35796
+ if (g.Market !== void 0) out["Market"] = g.Market;
35797
+ if (g.BidPrice !== void 0) out["BidPrice"] = g.BidPrice;
35798
+ if (g.CustomAmiId !== void 0) out["CustomAmiId"] = g.CustomAmiId;
35799
+ return out;
35800
+ }
35801
+ reverseInstanceFleet(f) {
35802
+ const out = {};
35803
+ if (f.Name !== void 0) out["Name"] = f.Name;
35804
+ if (f.TargetOnDemandCapacity !== void 0) out["TargetOnDemandCapacity"] = f.TargetOnDemandCapacity;
35805
+ if (f.TargetSpotCapacity !== void 0) out["TargetSpotCapacity"] = f.TargetSpotCapacity;
35806
+ return out;
35807
+ }
35522
35808
  };
35523
35809
 
35524
35810
  //#endregion
@@ -37608,6 +37894,54 @@ function tagMapsEqual(a, b) {
37608
37894
  if (aKeys.length !== Object.keys(b).length) return false;
37609
37895
  return aKeys.every((k) => b[k] === a[k]);
37610
37896
  }
37897
+ /** Default branch for the `Code` seed commit when `BranchName` is omitted. */
37898
+ const DEFAULT_SEED_BRANCH = "main";
37899
+ /**
37900
+ * Coerce a CFn scalar value to a string. Post-intrinsic-resolution values are
37901
+ * strings in practice; numbers / booleans are stringified and any other shape
37902
+ * (object / null / undefined) collapses to `''` — avoids stringifying an
37903
+ * object to the useless `'[object Object]'`.
37904
+ */
37905
+ function scalarToString(value) {
37906
+ if (typeof value === "string") return value;
37907
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
37908
+ return "";
37909
+ }
37910
+ /**
37911
+ * Convert the CFn `Triggers` list shape to the CodeCommit SDK's
37912
+ * `RepositoryTrigger[]` (PascalCase → camelCase). `Branches` is ALWAYS
37913
+ * emitted (defaulting to `[]` when the template omits it) — CodeCommit's
37914
+ * `PutRepositoryTriggers` rejects a trigger whose `branches` is null with
37915
+ * "Repository trigger branch name list cannot be null", and an empty array
37916
+ * means "all branches" (matching CFn's default when `Branches` is absent).
37917
+ * `CustomData` is truly optional (emitted only when present) so a re-order /
37918
+ * equality comparison stays stable. `Events` / `Branches` are coerced to
37919
+ * string arrays.
37920
+ */
37921
+ function toSdkTriggers(triggers) {
37922
+ if (!Array.isArray(triggers) || triggers.length === 0) return [];
37923
+ return triggers.map((t) => {
37924
+ const events = Array.isArray(t?.Events) ? t.Events : [];
37925
+ const branches = Array.isArray(t?.Branches) ? t.Branches : [];
37926
+ const trigger = {
37927
+ name: scalarToString(t?.Name),
37928
+ destinationArn: scalarToString(t?.DestinationArn),
37929
+ events: events.map((e) => scalarToString(e)),
37930
+ branches: branches.map((b) => scalarToString(b))
37931
+ };
37932
+ if (t?.CustomData !== void 0 && t.CustomData !== null) trigger.customData = scalarToString(t.CustomData);
37933
+ return trigger;
37934
+ });
37935
+ }
37936
+ /**
37937
+ * Order-sensitive structural equality for two mapped SDK trigger lists, used
37938
+ * to skip a redundant `PutRepositoryTriggers` when the template `Triggers`
37939
+ * block is unchanged. Compared as canonical JSON so `undefined` optional
37940
+ * fields (`customData` / `branches`) collapse identically on both sides.
37941
+ */
37942
+ function triggersEqual(a, b) {
37943
+ return JSON.stringify(a) === JSON.stringify(b);
37944
+ }
37611
37945
  /**
37612
37946
  * AWS CodeCommit Repository Provider
37613
37947
  *
@@ -37626,19 +37960,25 @@ function tagMapsEqual(a, b) {
37626
37960
  */
37627
37961
  var CodeCommitRepositoryProvider = class {
37628
37962
  client;
37963
+ s3Client;
37629
37964
  providerRegion = process.env["AWS_REGION"];
37630
37965
  logger = getLogger().child("CodeCommitRepositoryProvider");
37631
37966
  handledProperties = new Map([["AWS::CodeCommit::Repository", new Set([
37632
37967
  "RepositoryName",
37633
37968
  "RepositoryDescription",
37634
37969
  "KmsKeyId",
37635
- "Tags"
37970
+ "Tags",
37971
+ "Code",
37972
+ "Triggers"
37636
37973
  ])]]);
37637
- unhandledByDesign = new Map([["AWS::CodeCommit::Repository", new Map([["Code", "CFn-only seed-content orchestration (S3 zip unpacked into an initial commit on a chosen branch); not wired in v1 — pre-flight rejects templates carrying it so nothing is silently dropped; a follow-up could implement it via CreateCommit/PutFile"], ["Triggers", "repository trigger management not wired in v1 — pre-flight rejects templates carrying it so nothing is silently dropped; a follow-up could implement it via PutRepositoryTriggers"]])]]);
37638
37974
  getClient() {
37639
37975
  if (!this.client) this.client = new CodeCommitClient(this.providerRegion ? { region: this.providerRegion } : {});
37640
37976
  return this.client;
37641
37977
  }
37978
+ getS3Client() {
37979
+ if (!this.s3Client) this.s3Client = new S3Client(this.providerRegion ? { region: this.providerRegion } : {});
37980
+ return this.s3Client;
37981
+ }
37642
37982
  /**
37643
37983
  * Build the `Fn::GetAtt` attribute map from a `RepositoryMetadata`
37644
37984
  * response. `RepositoryId` is additionally stored so the intrinsic
@@ -37671,9 +38011,26 @@ var CodeCommitRepositoryProvider = class {
37671
38011
  ...tags ? { tags } : {}
37672
38012
  }))).repositoryMetadata;
37673
38013
  if (!metadata?.repositoryName) throw new Error("CreateRepository did not return repository metadata");
37674
- this.logger.debug(`Successfully created CodeCommit Repository ${logicalId}: ${metadata.repositoryName}`);
38014
+ const createdName = metadata.repositoryName;
38015
+ try {
38016
+ const code = properties["Code"];
38017
+ if (code) await this.seedInitialCommit(createdName, code);
38018
+ const triggers = properties["Triggers"];
38019
+ if (Array.isArray(triggers) && triggers.length > 0) {
38020
+ await this.getClient().send(new PutRepositoryTriggersCommand({
38021
+ repositoryName: createdName,
38022
+ triggers: toSdkTriggers(triggers)
38023
+ }));
38024
+ this.logger.debug(`Applied ${triggers.length} trigger(s) to ${createdName}`);
38025
+ }
38026
+ } catch (postCreateError) {
38027
+ this.logger.warn(`Post-create step failed for CodeCommit Repository ${logicalId}; deleting the just-created repository ${createdName} to avoid an orphan`);
38028
+ await this.bestEffortDelete(createdName);
38029
+ throw postCreateError;
38030
+ }
38031
+ this.logger.debug(`Successfully created CodeCommit Repository ${logicalId}: ${createdName}`);
37675
38032
  return {
37676
- physicalId: metadata.repositoryName,
38033
+ physicalId: createdName,
37677
38034
  attributes: this.toAttributes(metadata)
37678
38035
  };
37679
38036
  } catch (error) {
@@ -37691,7 +38048,11 @@ var CodeCommitRepositoryProvider = class {
37691
38048
  * CFn's `Ref` value — survives the rename), RepositoryDescription
37692
38049
  * (UpdateRepositoryDescription), KmsKeyId (UpdateRepositoryEncryptionKey),
37693
38050
  * Tags (TagResource / UntagResource — full tag removal handled explicitly,
37694
- * see the ECR Tags regression class in issue #981).
38051
+ * see the ECR Tags regression class in issue #981), Triggers
38052
+ * (PutRepositoryTriggers — a full-set replace, so a dropped or fully-removed
38053
+ * `Triggers` property is applied by putting the new/empty set; issue #1066).
38054
+ * `Code` is create-only seed content (CFn ignores it on update) and is NOT
38055
+ * re-applied here.
37695
38056
  *
37696
38057
  * A rename returns the NEW repository name as `physicalId` with
37697
38058
  * `wasReplaced: false`; the deploy engine persists the returned physical
@@ -37753,6 +38114,14 @@ var CodeCommitRepositoryProvider = class {
37753
38114
  this.logger.debug(`Updated tags for ${currentName}`);
37754
38115
  } else this.logger.warn(`Could not resolve ARN for CodeCommit Repository ${currentName}; tag update skipped`);
37755
38116
  }
38117
+ const newSdkTriggers = toSdkTriggers(properties["Triggers"]);
38118
+ if (!triggersEqual(newSdkTriggers, toSdkTriggers(previousProperties["Triggers"]))) {
38119
+ await this.getClient().send(new PutRepositoryTriggersCommand({
38120
+ repositoryName: currentName,
38121
+ triggers: newSdkTriggers
38122
+ }));
38123
+ this.logger.debug(`Updated triggers for ${currentName} (${newSdkTriggers.length} trigger(s))`);
38124
+ }
37756
38125
  metadata = await this.getRepositoryMetadata(currentName);
37757
38126
  return {
37758
38127
  physicalId: currentName,
@@ -37882,12 +38251,15 @@ var CodeCommitRepositoryProvider = class {
37882
38251
  * State property paths this provider cannot read back from AWS, skipped by
37883
38252
  * the drift comparator to avoid a guaranteed false positive.
37884
38253
  *
37885
- * `Code` (create-only S3-zip seed content unpacked into an initial commit)
37886
- * and `Triggers` (repository trigger management, not wired) are both
37887
- * `unhandledByDesign` normally rejected pre-flight, but a state written
37888
- * under `--allow-unsupported-properties` could still carry them, and neither
37889
- * is read back by `readCurrentState`, so exclude both defensively to avoid a
37890
- * guaranteed false-positive drift on every run.
38254
+ * `Code` is create-only S3-zip seed content unpacked into the initial
38255
+ * commit there is no read-back to compare against (the commit is git
38256
+ * history, not a repository attribute), so it can never meaningfully drift.
38257
+ * `Triggers` IS wired on the write side (create + update via
38258
+ * `PutRepositoryTriggers`), but `readCurrentState` does not yet fetch
38259
+ * `GetRepositoryTriggers`, so comparing a state that carries `Triggers`
38260
+ * against an `observedProperties` that omits it would be a guaranteed false
38261
+ * positive. Both are therefore excluded here; a follow-up can add
38262
+ * `GetRepositoryTriggers` read-back and drop `Triggers` from this list.
37891
38263
  */
37892
38264
  getDriftUnknownPaths(_resourceType) {
37893
38265
  return ["Code", "Triggers"];
@@ -37938,6 +38310,67 @@ var CodeCommitRepositoryProvider = class {
37938
38310
  return null;
37939
38311
  }
37940
38312
  /**
38313
+ * Seed the repository's initial commit from the CFn `Code` property.
38314
+ *
38315
+ * CFn's `Code` orchestration downloads the S3 ZIP, unpacks it, and creates
38316
+ * the repository's first commit on `BranchName` (default `main`). cdkd
38317
+ * reproduces that here: `GetObject` the ZIP, unpack every file entry
38318
+ * (directories are implied by file paths — CodeCommit has no empty-dir
38319
+ * concept), and issue a single `CreateCommit` carrying all files as
38320
+ * `putFiles`. This is create-only: CFn ignores `Code` on update, and so
38321
+ * does cdkd (`update()` never calls this).
38322
+ *
38323
+ * A ZIP with no file entries is a no-op (warn + skip) rather than a hard
38324
+ * failure — CodeCommit rejects a `CreateCommit` with an empty `putFiles`.
38325
+ */
38326
+ async seedInitialCommit(repositoryName, code) {
38327
+ const bucket = code.S3?.Bucket;
38328
+ const key = code.S3?.Key;
38329
+ if (typeof bucket !== "string" || typeof key !== "string" || !bucket || !key) throw new Error("Code.S3 requires string Bucket and Key");
38330
+ const versionId = typeof code.S3?.ObjectVersion === "string" ? code.S3.ObjectVersion : void 0;
38331
+ const branchName = typeof code.BranchName === "string" && code.BranchName ? code.BranchName : DEFAULT_SEED_BRANCH;
38332
+ const obj = await this.getS3Client().send(new GetObjectCommand({
38333
+ Bucket: bucket,
38334
+ Key: key,
38335
+ ...versionId && { VersionId: versionId }
38336
+ }));
38337
+ if (!obj.Body) throw new Error(`Code.S3 object s3://${bucket}/${key} returned an empty body`);
38338
+ const zipBytes = await obj.Body.transformToByteArray();
38339
+ const zip = new AdmZip(Buffer.from(zipBytes));
38340
+ const putFiles = [];
38341
+ for (const entry of zip.getEntries()) {
38342
+ if (entry.isDirectory) continue;
38343
+ putFiles.push({
38344
+ filePath: entry.entryName,
38345
+ fileContent: entry.getData()
38346
+ });
38347
+ }
38348
+ if (putFiles.length === 0) {
38349
+ this.logger.warn(`Code.S3 object s3://${bucket}/${key} contained no files; skipping seed commit for ${repositoryName}`);
38350
+ return;
38351
+ }
38352
+ await this.getClient().send(new CreateCommitCommand({
38353
+ repositoryName,
38354
+ branchName,
38355
+ commitMessage: "Initial commit",
38356
+ putFiles
38357
+ }));
38358
+ this.logger.debug(`Seeded ${repositoryName} with ${putFiles.length} file(s) on branch ${branchName}`);
38359
+ }
38360
+ /**
38361
+ * Best-effort delete used to roll back a just-created repository when a
38362
+ * post-create step (`Code` seed / `Triggers`) fails. Never throws — the
38363
+ * original post-create error is what the caller re-throws; a cleanup
38364
+ * failure is logged so the orphan is surfaced.
38365
+ */
38366
+ async bestEffortDelete(repositoryName) {
38367
+ try {
38368
+ await this.getClient().send(new DeleteRepositoryCommand$1({ repositoryName }));
38369
+ } catch (cleanupError) {
38370
+ this.logger.warn(`Failed to clean up CodeCommit Repository ${repositoryName} after a post-create failure: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
38371
+ }
38372
+ }
38373
+ /**
37941
38374
  * Fetch the repository's metadata via `GetRepository`. Throws
37942
38375
  * `RepositoryDoesNotExistException` through to the caller.
37943
38376
  */
@@ -60264,7 +60697,7 @@ function reorderArgs(argv) {
60264
60697
  async function main() {
60265
60698
  installPipeCloseHandler();
60266
60699
  const program = new Command();
60267
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.246.0");
60700
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.248.0");
60268
60701
  program.addCommand(createBootstrapCommand());
60269
60702
  program.addCommand(createSynthCommand());
60270
60703
  program.addCommand(createListCommand());