@go-to-k/cdkd 0.252.0 → 0.252.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/dist/cli.js CHANGED
@@ -1888,7 +1888,7 @@ const FLUSH_INTERVAL_MS = 2e3;
1888
1888
  const FLUSH_EVENT_THRESHOLD = 50;
1889
1889
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
1890
1890
  function getCdkdVersion() {
1891
- return "0.252.0";
1891
+ return "0.252.1";
1892
1892
  }
1893
1893
  /**
1894
1894
  * Generate a time-sortable unique run id, e.g.
@@ -35555,6 +35555,38 @@ var FSxFileSystemProvider = class {
35555
35555
  ];
35556
35556
  }
35557
35557
  /**
35558
+ * Plain-string arrays FSx returns as unordered sets.
35559
+ *
35560
+ * - `WindowsConfiguration.Aliases` — DNS alias names (`files.example.com`).
35561
+ * Alternate names the file system answers to; the API documents no
35562
+ * ordering semantics and no name is privileged over another, so a
35563
+ * `DescribeFileSystems` reorder carries no meaning.
35564
+ *
35565
+ * This does not match the shared normalizer's AWS-id / ARN heuristic, so
35566
+ * without the declaration a reorder would surface as phantom drift. Declared
35567
+ * here rather than sorted in `readWindowsConfiguration` so the sort applies
35568
+ * to BOTH comparison sides — see
35569
+ * {@link ResourceProvider.getDriftUnorderedPaths}.
35570
+ *
35571
+ * Deliberately NOT declared:
35572
+ *
35573
+ * - `WindowsConfiguration.SelfManagedActiveDirectoryConfiguration.DnsIps` —
35574
+ * the API reference describes it only as "A list of IP addresses of DNS
35575
+ * servers or domain controllers in the self-managed AD directory"
35576
+ * (https://docs.aws.amazon.com/fsx/latest/APIReference/API_SelfManagedActiveDirectoryConfiguration.html),
35577
+ * with no statement that order is insignificant. DNS resolver lists are
35578
+ * conventionally preference-ordered (primary first), and if FSx honors
35579
+ * that when joining the domain, sorting would HIDE a real reorder. A
35580
+ * false positive is visible and correctable; silently hiding drift is
35581
+ * not. Same reasoning excludes ElastiCache `PreferredAvailabilityZones`.
35582
+ * - `OntapConfiguration.RouteTableIds` / `OpenZFSConfiguration.RouteTableIds`
35583
+ * — their `rtb-` elements already match the shared id heuristic.
35584
+ */
35585
+ getDriftUnorderedPaths(resourceType) {
35586
+ if (resourceType !== "AWS::FSx::FileSystem") return [];
35587
+ return ["WindowsConfiguration.Aliases"];
35588
+ }
35589
+ /**
35558
35590
  * Read the AWS-current file system configuration in CFn-property shape
35559
35591
  * via `DescribeFileSystems`. Lustre data-repository fields (`ImportPath`
35560
35592
  * / `ExportPath` / `AutoImportPolicy` / `ImportedFileChunkSize`) are
@@ -44691,6 +44723,19 @@ function createDiffCommand() {
44691
44723
  * on BOTH sides before the diff. These two passes were surfaced by dogfooding
44692
44724
  * the sibling cdk-real-drift (cdkrd) tool, which hit the same false-positive
44693
44725
  * classes against the same kind of AWS-snapshot baseline.
44726
+ *
44727
+ * Plain-string arrays are NOT canonicalized by the two heuristic passes above,
44728
+ * because a scalar list can be order-significant. But several CFn inputs are
44729
+ * semantically unordered sets of plain strings (FSx `WindowsConfiguration.Aliases`,
44730
+ * `...SelfManagedActiveDirectoryConfiguration.DnsIps`, ...), so
44731
+ * {@link canonicalizeUnorderedArraysAtPaths} sorts them at an explicit,
44732
+ * provider-declared path list only — an opt-in seam mirroring
44733
+ * `getDriftUnknownPaths` (see `ResourceProvider.getDriftUnorderedPaths`).
44734
+ * Doing it HERE rather than inside the provider's reverse-mapper is load-bearing:
44735
+ * the normalizer runs on BOTH comparison sides, so it stays correct for the
44736
+ * `properties`-fallback baseline (a resource deployed before observed-capture,
44737
+ * whose baseline is the user's TEMPLATE order). Sorting only the AWS read side
44738
+ * would manufacture drift on exactly that path.
44694
44739
  */
44695
44740
  function canonicalizeTagListsDeep(v) {
44696
44741
  if (Array.isArray(v)) {
@@ -44725,6 +44770,72 @@ function canonicalizeIdArraysDeep(v) {
44725
44770
  }
44726
44771
  return v;
44727
44772
  }
44773
+ /**
44774
+ * Match a dotted property path against a provider-declared path list.
44775
+ *
44776
+ * An entry matches when the path is exactly equal to it, OR when the entry is
44777
+ * a prefix followed by `.`. Every entry is therefore a SUBTREE declaration:
44778
+ * `'VpcConfig'` matches `VpcConfig`, `VpcConfig.SubnetIds`, and anything
44779
+ * deeper. There is no leaf-only form.
44780
+ *
44781
+ * Shared by the two provider-declared path lists so their semantics cannot
44782
+ * drift apart: `getDriftUnknownPaths` (via `isIgnoredPath` in
44783
+ * `drift-calculator.ts`) and `getDriftUnorderedPaths` (via
44784
+ * {@link canonicalizeUnorderedArraysAtPaths} below).
44785
+ */
44786
+ function matchesPathPrefix(path, entries) {
44787
+ for (const entry of entries) {
44788
+ if (path === entry) return true;
44789
+ if (path.startsWith(`${entry}.`)) return true;
44790
+ }
44791
+ return false;
44792
+ }
44793
+ /**
44794
+ * Sort plain-string arrays found at one of the provider-declared `paths`, and
44795
+ * ONLY there. Unlike the two heuristic passes above, this one is opt-in per
44796
+ * resource type via `ResourceProvider.getDriftUnorderedPaths` — a scalar list
44797
+ * is order-significant unless the provider says otherwise.
44798
+ *
44799
+ * Paths use dot-notation for nested keys and are matched by the shared
44800
+ * {@link matchesPathPrefix} rule, the same one `getDriftUnknownPaths` uses. So
44801
+ * `'WindowsConfiguration'` covers every plain-string array in that subtree, and
44802
+ * `'WindowsConfiguration.Aliases'` covers that path and everything beneath it
44803
+ * (every entry is a subtree declaration — there is no leaf-only form).
44804
+ *
44805
+ * One semantic DIVERGENCE from `getDriftUnknownPaths`, required for this pass
44806
+ * to work: `isIgnoredPath` never sees a path that crosses an array, because the
44807
+ * comparator's `diffAt` compares arrays wholesale via `deepEqual` and never
44808
+ * descends into elements. This walk DOES descend into array elements, giving
44809
+ * each element its parent's path (array indices never appear in paths). So
44810
+ * `'Items.Aliases'` is meaningful here — it reaches an `Aliases` array inside
44811
+ * each element of `Items` — while being inert as an ignore-path. The divergence
44812
+ * is strictly more permissive; nothing that matches for ignore-paths fails to
44813
+ * match here.
44814
+ *
44815
+ * Only arrays whose every element is a plain string are sorted, so a
44816
+ * mis-declared path can never reorder object or mixed-type elements. Nested
44817
+ * arrays are also left alone: an array element that is itself an array is not
44818
+ * descended into, so `{P: [['b','a']]}` never has its INNER list sorted by a
44819
+ * `'P'` declaration (no CFn shape in tree is an array-of-arrays, and sorting
44820
+ * one would contradict the plain-string-elements-only rule).
44821
+ */
44822
+ function canonicalizeUnorderedArraysAtPaths(v, paths) {
44823
+ if (paths.length === 0) return v;
44824
+ return walkUnordered(v, "", paths);
44825
+ }
44826
+ function walkUnordered(v, path, paths) {
44827
+ if (Array.isArray(v)) {
44828
+ const mapped = v.map((el) => Array.isArray(el) ? el : walkUnordered(el, path, paths));
44829
+ if (mapped.length > 1 && matchesPathPrefix(path, paths) && mapped.every((el) => typeof el === "string")) return [...mapped].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
44830
+ return mapped;
44831
+ }
44832
+ if (v && typeof v === "object") {
44833
+ const out = {};
44834
+ for (const [k, val] of Object.entries(v)) out[k] = walkUnordered(val, path === "" ? k : `${path}.${k}`, paths);
44835
+ return out;
44836
+ }
44837
+ return v;
44838
+ }
44728
44839
 
44729
44840
  //#endregion
44730
44841
  //#region src/analyzer/drift-calculator.ts
@@ -44784,25 +44895,35 @@ function canonicalizeIdArraysDeep(v) {
44784
44895
  * it is exactly equal to the entry, or when the entry is a prefix followed
44785
44896
  * by `.` — so `'Code'` excludes the whole `Code` subtree, and
44786
44897
  * `'VpcConfig.SubnetIds'` excludes only that leaf.
44898
+ *
44899
+ * `options.unorderedPaths` is supplied by the provider (via
44900
+ * `getDriftUnorderedPaths`) for plain-string arrays that are semantically
44901
+ * UNORDERED sets — e.g. FSx `WindowsConfiguration.Aliases`. Those are sorted on
44902
+ * BOTH sides before comparison so an AWS-side reorder is not phantom drift.
44903
+ * Path matching uses the same prefix rule as `ignorePaths`.
44787
44904
  */
44788
44905
  function calculateResourceDrift(stateProperties, awsProperties, options) {
44789
44906
  const drifts = [];
44790
44907
  const ignore = options?.ignorePaths ?? [];
44791
44908
  const union = options?.unionWalkObjects ?? false;
44792
- stateProperties = canonicalizeIdArraysDeep(canonicalizeTagListsDeep(stateProperties));
44793
- awsProperties = canonicalizeIdArraysDeep(canonicalizeTagListsDeep(awsProperties));
44909
+ const unordered = options?.unorderedPaths ?? [];
44910
+ stateProperties = canonicalizeUnorderedArraysAtPaths(canonicalizeIdArraysDeep(canonicalizeTagListsDeep(stateProperties)), unordered);
44911
+ awsProperties = canonicalizeUnorderedArraysAtPaths(canonicalizeIdArraysDeep(canonicalizeTagListsDeep(awsProperties)), unordered);
44794
44912
  for (const key of Object.keys(stateProperties)) {
44795
44913
  if (isIgnoredPath(key, ignore)) continue;
44796
44914
  diffAt(key, stateProperties[key], awsProperties[key], drifts, ignore, union);
44797
44915
  }
44798
44916
  return drifts;
44799
44917
  }
44918
+ /**
44919
+ * Thin alias over the shared {@link matchesPathPrefix} rule, kept as a named
44920
+ * function because "ignored" is what the path list means at these call sites.
44921
+ * Sharing the implementation with `getDriftUnorderedPaths` is deliberate: both
44922
+ * lists are provider-declared and documented as reading the same way, so they
44923
+ * must not be able to drift apart.
44924
+ */
44800
44925
  function isIgnoredPath(path, ignorePaths) {
44801
- for (const entry of ignorePaths) {
44802
- if (path === entry) return true;
44803
- if (path.startsWith(`${entry}.`)) return true;
44804
- }
44805
- return false;
44926
+ return matchesPathPrefix(path, ignorePaths);
44806
44927
  }
44807
44928
  /**
44808
44929
  * Recursive worker. Pushes drift entries into `out` rather than
@@ -45204,10 +45325,12 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
45204
45325
  continue;
45205
45326
  }
45206
45327
  const ignorePaths = provider.getDriftUnknownPaths ? provider.getDriftUnknownPaths(resource.resourceType) : [];
45328
+ const unorderedPaths = provider.getDriftUnorderedPaths ? provider.getDriftUnorderedPaths(resource.resourceType) : [];
45207
45329
  const useObserved = resource.observedProperties !== void 0;
45208
45330
  const changes = calculateResourceDrift(useObserved ? resource.observedProperties : resource.properties ?? {}, aws, {
45209
45331
  ignorePaths,
45210
- unionWalkObjects: useObserved
45332
+ unionWalkObjects: useObserved,
45333
+ unorderedPaths
45211
45334
  });
45212
45335
  if (changes.length === 0) outcomes.push({
45213
45336
  kind: "clean",
@@ -62017,7 +62140,7 @@ function reorderArgs(argv) {
62017
62140
  async function main() {
62018
62141
  installPipeCloseHandler();
62019
62142
  const program = new Command();
62020
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.252.0");
62143
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.252.1");
62021
62144
  program.addCommand(createBootstrapCommand());
62022
62145
  program.addCommand(createSynthCommand());
62023
62146
  program.addCommand(createListCommand());