@go-to-k/cdkd 0.221.3 → 0.221.5

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.
@@ -4080,6 +4080,53 @@ var TemplateParser = class {
4080
4080
  countResources(template) {
4081
4081
  return Object.keys(template.Resources).length;
4082
4082
  }
4083
+ /**
4084
+ * Produce a copy of the template with every resource whose `Condition:`
4085
+ * key resolves to `false` removed from `Resources` (issue #840).
4086
+ *
4087
+ * CloudFormation does not strip condition-gated resources at synth time —
4088
+ * CDK emits `PremiumOnlyParam` into `Resources` with a `Condition: IsPremium`
4089
+ * key REGARDLESS of the parameter value, and the deploy engine (CFn, or in
4090
+ * cdkd's case cdkd itself) is responsible for excluding the resource when
4091
+ * its condition evaluates false. cdkd evaluates the `Conditions` section
4092
+ * (for `Fn::If` resolution) but, before this helper, never consulted the
4093
+ * resource-level `Condition:` key — so a condition-false resource was:
4094
+ * - CREATED anyway on first deploy (it sat in the desired set), and
4095
+ * - never DELETED when its condition flipped true -> false on a redeploy
4096
+ * (it stayed in the desired set, diffing as NO_CHANGE).
4097
+ *
4098
+ * By pruning condition-false resources here, the rest of the pipeline
4099
+ * (type/property validation, DAG build, diff) sees the CFn-effective
4100
+ * resource set: a now-condition-false resource that exists in prior state
4101
+ * but is absent from the pruned template falls through the diff's existing
4102
+ * "in state but not in desired template -> DELETE" path, exactly as CFn
4103
+ * removes it. A resource whose `Condition` key names an unknown condition,
4104
+ * or whose condition evaluated `true`, is kept.
4105
+ *
4106
+ * The returned template shares every untouched object reference with the
4107
+ * input (only `Resources` is rebuilt) — callers must treat it as read-only,
4108
+ * which the deploy pipeline already does.
4109
+ *
4110
+ * @param template The synthesized template (raw, with all condition-gated
4111
+ * resources still present).
4112
+ * @param conditions The evaluated `Conditions` map (name -> boolean) from
4113
+ * `IntrinsicFunctionResolver.evaluateConditions`.
4114
+ */
4115
+ filterResourcesByCondition(template, conditions) {
4116
+ const filteredResources = {};
4117
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
4118
+ const conditionName = resource.Condition;
4119
+ if (conditionName !== void 0 && conditions[conditionName] === false) {
4120
+ this.logger.debug(`Excluding resource ${logicalId} — condition ${conditionName} is false`);
4121
+ continue;
4122
+ }
4123
+ filteredResources[logicalId] = resource;
4124
+ }
4125
+ return {
4126
+ ...template,
4127
+ Resources: filteredResources
4128
+ };
4129
+ }
4083
4130
  };
4084
4131
 
4085
4132
  //#endregion
@@ -5533,13 +5580,36 @@ var IntrinsicFunctionResolver = class {
5533
5580
  const conditions = {};
5534
5581
  const templateConditions = context.template.Conditions;
5535
5582
  if (!templateConditions || typeof templateConditions !== "object") return conditions;
5536
- for (const [name, definition] of Object.entries(templateConditions)) try {
5537
- const result = await this.resolveValue(definition, context);
5538
- conditions[name] = Boolean(result);
5539
- this.logger.debug(`Evaluated condition ${name} = ${conditions[name]}`);
5583
+ const inProgress = /* @__PURE__ */ new Set();
5584
+ const evaluateByName = async (name) => {
5585
+ if (name in conditions) return conditions[name];
5586
+ if (inProgress.has(name)) throw new Error(`Circular condition reference detected involving condition "${name}"`);
5587
+ const definition = templateConditions[name];
5588
+ if (definition === void 0) {
5589
+ this.logger.warn(`Condition ${name} not found in template, assuming false`);
5590
+ conditions[name] = false;
5591
+ return false;
5592
+ }
5593
+ inProgress.add(name);
5594
+ try {
5595
+ const result = await this.resolveValue(definition, {
5596
+ ...context,
5597
+ conditionResolver: evaluateByName
5598
+ });
5599
+ const value = Boolean(result);
5600
+ conditions[name] = value;
5601
+ this.logger.debug(`Evaluated condition ${name} = ${value}`);
5602
+ return value;
5603
+ } finally {
5604
+ inProgress.delete(name);
5605
+ }
5606
+ };
5607
+ for (const name of Object.keys(templateConditions)) try {
5608
+ await evaluateByName(name);
5540
5609
  } catch (error) {
5541
5610
  this.logger.warn(`Failed to evaluate condition ${name}: ${error instanceof Error ? error.message : String(error)}, assuming false`);
5542
5611
  conditions[name] = false;
5612
+ inProgress.delete(name);
5543
5613
  }
5544
5614
  return conditions;
5545
5615
  }
@@ -5561,6 +5631,7 @@ var IntrinsicFunctionResolver = class {
5561
5631
  if ("Fn::Split" in obj) return await this.resolveSplit(obj["Fn::Split"], context);
5562
5632
  if ("Fn::If" in obj) return await this.resolveIf(obj["Fn::If"], context);
5563
5633
  if ("Fn::Equals" in obj) return await this.resolveEquals(obj["Fn::Equals"], context);
5634
+ if (context.conditionResolver && "Condition" in obj && Object.keys(obj).length === 1 && typeof obj["Condition"] === "string") return await this.resolveConditionReference(obj["Condition"], context);
5564
5635
  if ("Fn::And" in obj) return await this.resolveAnd(obj["Fn::And"], context);
5565
5636
  if ("Fn::Or" in obj) return await this.resolveOr(obj["Fn::Or"], context);
5566
5637
  if ("Fn::Not" in obj) return await this.resolveNot(obj["Fn::Not"], context);
@@ -6006,6 +6077,23 @@ var IntrinsicFunctionResolver = class {
6006
6077
  return result;
6007
6078
  }
6008
6079
  /**
6080
+ * Resolve a `{Condition: <name>}` named-condition reference (issue #840).
6081
+ *
6082
+ * Inside `Fn::And` / `Fn::Or` / `Fn::Not` a CFn Condition may reference
6083
+ * another named condition. When the resolver is mid-`evaluateConditions`
6084
+ * the `conditionResolver` hook is present and lazily evaluates the
6085
+ * referenced condition (recursing + memoizing + cycle-guarding) so the
6086
+ * result is order-independent. Outside that context (which is invalid CFn
6087
+ * but handled defensively) we fall back to the already-evaluated
6088
+ * `conditions` map, matching `Fn::If`'s warn-and-assume-false behavior.
6089
+ */
6090
+ async resolveConditionReference(conditionName, context) {
6091
+ if (context.conditionResolver) return await context.conditionResolver(conditionName);
6092
+ if (context.conditions && conditionName in context.conditions) return context.conditions[conditionName];
6093
+ this.logger.warn(`Condition ${conditionName} not found in context, assuming false`);
6094
+ return false;
6095
+ }
6096
+ /**
6009
6097
  * Resolve Fn::And intrinsic function
6010
6098
  *
6011
6099
  * Returns true if all conditions evaluate to true
@@ -11574,6 +11662,7 @@ var DeployEngine = class {
11574
11662
  lockManager;
11575
11663
  dagBuilder;
11576
11664
  diffCalculator;
11665
+ templateParser = new TemplateParser();
11577
11666
  providerRegistry;
11578
11667
  options;
11579
11668
  /**
@@ -11807,10 +11896,11 @@ var DeployEngine = class {
11807
11896
  }, stackName);
11808
11897
  const conditions = await this.resolver.evaluateConditions(context);
11809
11898
  this.logger.debug(`Evaluated ${Object.keys(conditions).length} conditions: ${Object.keys(conditions).join(", ")}`);
11810
- const resourceTypes = new Set(Object.values(template.Resources || {}).map((r) => r.Type).filter((type) => type !== "AWS::CDK::Metadata"));
11899
+ const effectiveTemplate = this.templateParser.filterResourcesByCondition(template, conditions);
11900
+ const resourceTypes = new Set(Object.values(effectiveTemplate.Resources || {}).map((r) => r.Type).filter((type) => type !== "AWS::CDK::Metadata"));
11811
11901
  this.providerRegistry.validateResourceTypes(resourceTypes);
11812
11902
  this.logger.debug(`All resource types validated`);
11813
- const resourcesForPropertyCheck = Object.entries(template.Resources || {}).filter(([, r]) => r.Type !== "AWS::CDK::Metadata").map(([logicalId, r]) => ({
11903
+ const resourcesForPropertyCheck = Object.entries(effectiveTemplate.Resources || {}).filter(([, r]) => r.Type !== "AWS::CDK::Metadata").map(([logicalId, r]) => ({
11814
11904
  logicalId,
11815
11905
  resourceType: r.Type,
11816
11906
  properties: r.Properties,
@@ -11818,17 +11908,17 @@ var DeployEngine = class {
11818
11908
  }));
11819
11909
  this.providerRegistry.validateResourceProperties(resourcesForPropertyCheck);
11820
11910
  this.logger.debug(`All resource properties validated`);
11821
- const dag = this.dagBuilder.buildGraph(template);
11911
+ const dag = this.dagBuilder.buildGraph(effectiveTemplate);
11822
11912
  const executionLevels = this.dagBuilder.getExecutionLevels(dag);
11823
11913
  this.logger.debug(`Dependency graph: ${executionLevels.length} execution levels`);
11824
11914
  const diffResolverContext = this.buildResolverContext({
11825
- template,
11915
+ template: effectiveTemplate,
11826
11916
  resources: currentState.resources,
11827
11917
  parameters: parameterValues,
11828
11918
  conditions
11829
11919
  }, stackName);
11830
11920
  const diffResolveFn = (value) => this.resolver.resolve(value, diffResolverContext);
11831
- const changes = await this.diffCalculator.calculateDiff(currentState, template, diffResolveFn);
11921
+ const changes = await this.diffCalculator.calculateDiff(currentState, effectiveTemplate, diffResolveFn);
11832
11922
  if (!this.diffCalculator.hasChanges(changes)) {
11833
11923
  this.logger.info("No changes detected. Stack is up to date.");
11834
11924
  if (!this.options.dryRun && this.observedCaptureTasks.size > 0) {
@@ -11882,7 +11972,7 @@ var DeployEngine = class {
11882
11972
  current: 0,
11883
11973
  total: createChanges.length + updateChanges.length + deleteChanges.length
11884
11974
  };
11885
- const { state: newState, actualCounts } = await this.executeDeployment(template, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress, migrationPending);
11975
+ const { state: newState, actualCounts } = await this.executeDeployment(effectiveTemplate, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress, migrationPending);
11886
11976
  await this.drainObservedCaptures(newState.resources);
11887
11977
  const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(newState));
11888
11978
  this.logger.debug(`State saved (ETag: ${newEtag})`);
@@ -12800,4 +12890,4 @@ var DeployEngine = class {
12800
12890
 
12801
12891
  //#endregion
12802
12892
  export { clearBucketRegionCache as $, AssetPublisher as A, getLegacyStateBucketName as B, DiffCalculator as C, S3StateBackend as D, LockManager as E, getDockerCmd as F, resolveStateBucketWithDefaultAndSource as G, resolveCaptureObservedState as H, runDockerForeground as I, CFN_TEMPLATE_URL_LIMIT as J, warnDeprecatedNoPrefixCliFlag as K, runDockerStreaming as L, WorkGraph as M, buildDockerImage as N, rebuildClientForBucketRegion as O, formatDockerLoginError as P, AssemblyReader as Q, Synthesizer as R, applyRoleArnIfSet as S, TemplateParser as T, resolveSkipPrefix as U, resolveApp as V, resolveStateBucketWithDefault as W, findLargeInlineResources as X, MIGRATE_TMP_PREFIX as Y, uploadCfnTemplate as Z, collectInlinePolicyNamesManagedBySiblings as _, withRetry as a, CloudControlProvider as b, extractDeploymentEventError as c, cyan as d, resolveBucketRegion as et, gray as f, IAMRoleProvider as g, yellow as h, withResourceDeadline as i, stringifyValue as j, shouldRetainResource as k, formatResourceLine as l, red as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isRetryableTransientError as o, green as p, CFN_TEMPLATE_BODY_LIMIT as q, DeployEngine as r, IMPLICIT_DELETE_DEPENDENCIES as s, DEFAULT_RESOURCE_TIMEOUT_MS as t, bold as u, ProviderRegistry as v, DagBuilder as w, IntrinsicFunctionResolver as x, findActionableSilentDrops as y, getDefaultStateBucketName as z };
12803
- //# sourceMappingURL=deploy-engine-CyPbcsXW.js.map
12893
+ //# sourceMappingURL=deploy-engine-DlXVqVTW.js.map