@go-to-k/cdkd 0.288.2 → 0.288.3

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.
@@ -1,8 +1,8 @@
1
1
  import { _ as withStackName, d as applyDefaultNameForFallback, f as generateResourceName, h as looksLikeCdkdGeneratedName, m as getCurrentStackName, n as getLogger, p as generateResourceNameWithFallback, r as isStdoutReservedForPayload, s as getLiveRenderer } from "./logger-Dw-3y48G.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-BRByznpy.js";
3
+ import { t as getCdkdVersion } from "./version-CEyZvTcB.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
- import { randomUUID } from "node:crypto";
5
+ import { createHash, randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
7
7
  import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
8
8
  import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand, NoSuchEntityException, PutRolePermissionsBoundaryCommand, PutRolePolicyCommand, RemoveRoleFromInstanceProfileCommand, TagRoleCommand, UntagRoleCommand, UpdateAssumeRolePolicyCommand, UpdateRoleCommand } from "@aws-sdk/client-iam";
@@ -9101,6 +9101,18 @@ function importableOutputs(state) {
9101
9101
  function exportNamesCarriedFrom(previous) {
9102
9102
  return previous.exportNames === void 0 ? {} : { exportNames: previous.exportNames };
9103
9103
  }
9104
+ /**
9105
+ * The `skippedOutputs` field to write when a save carries the PREVIOUS
9106
+ * record's `outputs` bag forward instead of re-resolving it (issue #2740). The
9107
+ * record describes that bag — which keys the deploy that wrote it could not
9108
+ * resolve — so it travels with the bag, exactly like {@link
9109
+ * exportNamesCarriedFrom}: absent stays absent, present stays as it was. A
9110
+ * writer that re-resolves outputs does NOT call this; it writes the set the
9111
+ * resolution produced, or omits the field when nothing was skipped.
9112
+ */
9113
+ function skippedOutputsCarriedFrom(previous) {
9114
+ return previous.skippedOutputs === void 0 ? {} : { skippedOutputs: previous.skippedOutputs };
9115
+ }
9104
9116
 
9105
9117
  //#endregion
9106
9118
  //#region src/types/rollback-journal.ts
@@ -16687,6 +16699,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
16687
16699
  "RoleArn"
16688
16700
  ]),
16689
16701
  silentDrop: /* @__PURE__ */ new Map([
16702
+ ["CapacityProviderConfiguration", "not yet implemented by cdkd"],
16690
16703
  ["FilesystemConfigurations", "not yet implemented by cdkd"],
16691
16704
  ["RequestHeaderConfiguration", "not yet implemented by cdkd"],
16692
16705
  ["Tags", "not yet implemented by cdkd"]
@@ -16805,7 +16818,8 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
16805
16818
  silentDrop: /* @__PURE__ */ new Map([
16806
16819
  ["EvaluationCriteria", "Absent from both the SDK PutMetricAlarm input and the aws-cdk-lib CfnAlarm L1 (a newer CFn-schema-only property ahead of SDK/CDK support); no wire path to forward it and no CDK app can emit it."],
16807
16820
  ["EvaluationInterval", "Absent from both the SDK PutMetricAlarm input and the aws-cdk-lib CfnAlarm L1 (a newer CFn-schema-only property ahead of SDK/CDK support); no wire path to forward it and no CDK app can emit it."],
16808
- ["EvaluationWindow", "not yet implemented by cdkd"]
16821
+ ["EvaluationWindow", "not yet implemented by cdkd"],
16822
+ ["WarmUpConfiguration", "not yet implemented by cdkd"]
16809
16823
  ]),
16810
16824
  createOnlyDrops: /* @__PURE__ */ new Set()
16811
16825
  }],
@@ -17633,7 +17647,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
17633
17647
  }],
17634
17648
  ["AWS::Glue::Connection", {
17635
17649
  handled: /* @__PURE__ */ new Set(["CatalogId", "ConnectionInput"]),
17636
- silentDrop: /* @__PURE__ */ new Map(),
17650
+ silentDrop: /* @__PURE__ */ new Map([["Tags", "not yet implemented by cdkd"]]),
17637
17651
  createOnlyDrops: /* @__PURE__ */ new Set()
17638
17652
  }],
17639
17653
  ["AWS::Glue::Crawler", {
@@ -18197,7 +18211,6 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
18197
18211
  ["UseLatestRestorableTime", "not yet implemented by cdkd"]
18198
18212
  ]),
18199
18213
  createOnlyDrops: /* @__PURE__ */ new Set([
18200
- "AvailabilityZones",
18201
18214
  "ClusterScalabilityType",
18202
18215
  "DBSystemId",
18203
18216
  "EngineMode",
@@ -23693,6 +23706,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
23693
23706
  case "ApiId": return physicalId;
23694
23707
  default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId);
23695
23708
  }
23709
+ if (resourceType === "AWS::ApiGatewayV2::Api") switch (attributeName) {
23710
+ case "ExecuteApiArn": return `arn:${partition}:execute-api:${region}:${accountId}:${physicalId}`;
23711
+ case "ApiId": return physicalId;
23712
+ default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId);
23713
+ }
23696
23714
  if (resourceType === "AWS::ServiceDiscovery::PrivateDnsNamespace" || resourceType === "AWS::ServiceDiscovery::HttpNamespace" || resourceType === "AWS::ServiceDiscovery::PublicDnsNamespace") switch (attributeName) {
23697
23715
  case "Arn": return `arn:${partition}:servicediscovery:${region}:${accountId}:namespace/${physicalId}`;
23698
23716
  case "Id": return physicalId;
@@ -26642,7 +26660,7 @@ var CloudControlProvider = class {
26642
26660
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
26643
26661
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
26644
26662
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
26645
- const { ASGProvider } = await import("./asg-provider-DUlRn84u.js").then((n) => n.n);
26663
+ const { ASGProvider } = await import("./asg-provider-CQrHElrl.js").then((n) => n.n);
26646
26664
  const asgProvider = new ASGProvider();
26647
26665
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
26648
26666
  }
@@ -31240,6 +31258,67 @@ function secretBearingExportNameWarning(outputKey, exportName, exposure) {
31240
31258
  return `Output ${stripControlChars(outputKey)} has an Export.Name that resolves to a value containing a secret ${shown}— skipping the export alias. An export name becomes a key in state.json and in the exports index, and redaction rewrites VALUES only, so publishing it would persist the secret in plaintext. Use a non-secret Export.Name.`;
31241
31259
  }
31242
31260
  /**
31261
+ * Test `key` for recorded secret plaintext and return how it may be shown.
31262
+ *
31263
+ * Reuses {@link stateKeySecretExposure} and the same `maskEveryOccurrence` the
31264
+ * warnings below use, rather than restating either: two spellings of "is this
31265
+ * key safe to print" would disagree on the boundary cases those two encode
31266
+ * (the whole-key match for a sub-floor needle, longest-needle-first masking).
31267
+ *
31268
+ * SANITISED WITH BOTH helpers, because neither is a superset of the other —
31269
+ * measured, after a first cut swapped one for the other and silently traded
31270
+ * one class of character for another (issue #2667 review):
31271
+ *
31272
+ * | input | `stripControlChars` | `displaySafe` |
31273
+ * | -------- | ------------------- | ------------- |
31274
+ * | `U+200E` | removed | KEPT |
31275
+ * | `U+200F` | removed | KEPT |
31276
+ * | `U+2028` | KEPT | replaced |
31277
+ * | `U+2029` | KEPT | replaced |
31278
+ *
31279
+ * `U+2028` / `U+2029` matter because this text is PERSISTED and re-rendered by
31280
+ * JSON and web log viewers that treat both as line terminators — the CI-log
31281
+ * surface this masking exists to protect, where an `Fn::Sub`-built export name
31282
+ * carrying one could forge a log line (`display-safe.ts` states that
31283
+ * rationale). `U+200E` / `U+200F` are the bidi MARKS, named as residuals in
31284
+ * that same file; they reorder rendered text without terminating a line. On a
31285
+ * path whose subject is a possibly-secret-bearing name in an operator's log,
31286
+ * neither loss is worth taking, and composing costs nothing.
31287
+ *
31288
+ * ORDER IS LOAD-BEARING, and for the OVERLAP set — not for the marks, which
31289
+ * an earlier revision of this comment named and measurement contradicted.
31290
+ * `displaySafe` never touches `U+200E` / `U+200F`, so those give identical
31291
+ * output either way. Where the two classes OVERLAP — `U+0000`-`U+001F`,
31292
+ * `U+007F`-`U+009F`, `U+202A`-`U+202E`, `U+2066`-`U+2069` — `stripControlChars`
31293
+ * DELETES while `displaySafe` replaces with a space, so strip-then-display
31294
+ * yields `"ab"` and display-then-strip yields `"a b"`. Stripping first keeps a
31295
+ * name carrying them from being padded out.
31296
+ *
31297
+ * `displaySafe` also `.trim()`s, which `stripControlChars` alone did not: a
31298
+ * display-shape change for a key with leading or trailing whitespace. Stated
31299
+ * because it is a real difference, not hidden.
31300
+ *
31301
+ * The sibling warnings in this file still use `stripControlChars` ALONE and
31302
+ * carry the `U+2028` half of the gap; widening that helper, or converting
31303
+ * them, changes call sites this issue does not touch, so it is filed as issue
31304
+ * [#2874](https://github.com/go-to-k/cdkd/issues/2874) rather than done here.
31305
+ */
31306
+ function secretSafeKeyDisplay(key, secrets) {
31307
+ const sanitise = (text) => displaySafe(stripControlChars(text));
31308
+ const shown = sanitise(key);
31309
+ const exposure = stateKeySecretExposure(shown, secrets) ?? stateKeySecretExposure(key, secrets);
31310
+ if (!exposure) return {
31311
+ kind: "safe",
31312
+ text: shown
31313
+ };
31314
+ const masked = sanitise(maskEveryOccurrence(shown, exposure));
31315
+ if (masked === shown) return { kind: "withheld" };
31316
+ return {
31317
+ kind: "masked",
31318
+ text: masked
31319
+ };
31320
+ }
31321
+ /**
31243
31322
  * Warning for a state KEY that already holds secret plaintext — the residue an
31244
31323
  * EARLIER binary left when it published an export name that resolved to one.
31245
31324
  *
@@ -32682,6 +32761,397 @@ async function createReplicationGroupFinalSnapshot(client, replicationGroupId, l
32682
32761
  });
32683
32762
  }
32684
32763
 
32764
+ //#endregion
32765
+ //#region src/analyzer/skipped-outputs.ts
32766
+ /**
32767
+ * The record `cdkd deploy` leaves behind for an Output it could NOT resolve
32768
+ * and SKIPPED, and the digest `cdkd diff` compares it against (issue
32769
+ * [#2740](https://github.com/go-to-k/cdkd/issues/2740)).
32770
+ *
32771
+ * The deploy side (`DeployEngine.handleOutputResolutionFailure`, default arm)
32772
+ * warns, stores `undefined` for the key and moves on, so a re-resolved
32773
+ * `state.outputs` lacks it (the no-change path keeps the previous bag whole
32774
+ * when any output fails, so a key that resolved on an EARLIER deploy can keep
32775
+ * its value beside a record — the reader checks absence first and ignores the
32776
+ * record then). The diff side resolves outputs with `skipDynamicReferences`, so a
32777
+ * failure that happens INSIDE a secret lookup — a JSON key the secret does not
32778
+ * hold, a reference assembled from another secret's value — does not reproduce
32779
+ * there: the value assembles into its token, the key is absent from state, and
32780
+ * `computeOutputsDiff` pushed an `ADD` the deploy would never perform, on every
32781
+ * run of an unchanged stack. The diff cannot tell from assembly alone that the
32782
+ * deploy would fail (a composed string carrying a secret reference is a
32783
+ * legitimate output), so it learns what the deploy learned instead:
32784
+ * `StackState.skippedOutputs` maps each skipped key to a digest of the
32785
+ * template inputs its resolution read, and the diff trusts the record only
32786
+ * while that digest is unchanged.
32787
+ *
32788
+ * ## What the digest covers, and why
32789
+ *
32790
+ * `skippedOutputDigest` hashes the output's OWN entry (`Value`, `Export`,
32791
+ * `Condition`, whatever else it declares) together with every top-level
32792
+ * template section EXCEPT `Resources` and `Outputs`:
32793
+ *
32794
+ * - The entry itself: repairing the `Value` expression is the obvious repair.
32795
+ * The `Export.Name` is in it too, because the alias pass routes an
32796
+ * `Export.Name` failure through the same handler, which blanks the output's
32797
+ * own key even when its value resolved — so a repaired or removed export
32798
+ * name must invalidate the record as well.
32799
+ * - `Parameters` (a `Ref`-built reference repaired through a parameter
32800
+ * default), `Conditions` (an `Fn::If` whose branch flips), `Mappings` (an
32801
+ * `Fn::FindInMap`-built key), and the remaining sections for the same
32802
+ * reason, spelled as one rule rather than a list that drifts: everything an
32803
+ * output's resolution can read besides resources. One VALUE inside them is
32804
+ * excluded — the `Default` of a `NoEcho: true` parameter, hashed as a
32805
+ * constant; see `withNoEchoDefaultsMasked` for why. That is the POSITION,
32806
+ * not the parameter: its `AllowedValues`, and any literal an author puts in
32807
+ * `Conditions`, `Rules` or `Metadata`, are hashed as before.
32808
+ * - NOT `Resources`: hashing the section would invalidate the record on every
32809
+ * unrelated resource edit. The output CAN be repaired from that side —
32810
+ * `collectSkippedOutputs` records both skip arms, and the quiet one is an
32811
+ * `Fn::GetAtt` whose attribute `constructAttribute` could not build — so a
32812
+ * digest alone would keep binding while the next deploy publishes the row
32813
+ * and its `Export.Name`, the phantom's inverse. {@link
32814
+ * bindingSkippedOutputs} closes that with the CHANGE MAP rather than the
32815
+ * digest: a key whose entry references a logical id with a pending resource
32816
+ * change does not bind. The diff has that map (it ran the resource diff
32817
+ * first); the deploy does not need it, because a stack with any resource
32818
+ * change takes the path that re-resolves every output and rewrites the
32819
+ * record. REFERENCE, not repair: whether an edit could actually make the
32820
+ * output resolvable is undecidable from a template, so an unrelated edit to
32821
+ * a referenced resource un-binds too and the key can preview as an `ADD`
32822
+ * again. The cost is bounded — a run with a changed resource is already
32823
+ * reporting that row, so `--fail` exits 1 regardless — while the case it
32824
+ * buys depends on whether the diff can resolve the output at all. For an
32825
+ * output reading an attribute that does not exist yet — the SECOND skip arm
32826
+ * this record covers, not the secret lookup issue #2740 was filed on — it is
32827
+ * the VERDICT and not the row: unresolvable in
32828
+ * both readings, so bound the diff reports the outputs settled while the
32829
+ * next deploy is about to publish that key and its `Export.Name`. For an
32830
+ * output the diff CAN resolve, whose reference is to a name rather than to
32831
+ * a pending attribute (an `Fn::Sub` over an SSM parameter's name, say), the
32832
+ * row IS the difference and reappears as an `ADD` the moment the record
32833
+ * stops binding.
32834
+ * - NOT the sibling `Outputs`: a CloudFormation output cannot reference
32835
+ * another output, so a sibling change cannot repair this one, and excluding
32836
+ * them keeps an unrelated output edit from producing a one-run phantom.
32837
+ *
32838
+ * What the digest CANNOT see is a repair outside the template — the secret
32839
+ * gained the JSON key, the SSM parameter was created, a parameter VALUE
32840
+ * handed to the engine rather than declared changed (a nested stack's inputs
32841
+ * from its parent, `DeployEngineOptions.parameters`; `Parameters`
32842
+ * DECLARATIONS are hashed, supplied values are not), or **cdkd itself was
32843
+ * upgraded**: the quiet skip arm is `constructAttribute` returning nothing,
32844
+ * so a provider that gains that attribute repairs the output while neither
32845
+ * the template nor any resource moves.
32846
+ *
32847
+ * They are blind spots for three different reasons, and only the first is
32848
+ * about knowledge. The secret's key set and the SSM parameter's existence
32849
+ * need the external lookup the diff deliberately refuses to make. A supplied
32850
+ * parameter VALUE is not one of those — a nested stack's inputs are resolved
32851
+ * and forwarded to the child diff (`resolveChildStackParameters`) — it is
32852
+ * simply outside what the digest hashes, because hashing values would make
32853
+ * the record depend on a caller's arguments rather than on the template. And
32854
+ * the writing binary's version is knowable and merely unstored: keeping it
32855
+ * beside the digest and un-binding on mismatch would close that one. All four
32856
+ * end the same way: the record keeps binding until the next deploy re-resolves
32857
+ * the output, and that deploy publishes the key — unless a SIBLING output is
32858
+ * still unresolved on a run with no resource change, where the engine keeps
32859
+ * the previous outputs bag wholesale and the key stays unpublished for a
32860
+ * further run (go-to-k/cdkd#2771, pre-existing and not introduced here).
32861
+ * Documented as the accepted limitation in `docs/cli-diff.md`; it is narrower
32862
+ * than the pre-#2740 behaviour, where the diff was wrong on EVERY run.
32863
+ *
32864
+ * A repair on the RESOURCE side is NOT on that list either, but the reason is
32865
+ * narrower than it first looks. The change map above closes it only when the
32866
+ * repair arrives WITH a template resource change, which is what makes the
32867
+ * resource show on the diff at all. An OUT-OF-BAND state write does not:
32868
+ * every writer that rebuilds state outside a deploy DROPS the record rather
32869
+ * than carry it (one enumerated exception, the partial-destroy snapshot, is
32870
+ * explained with the field) — `cdkd import` refreshes `attributes` for the
32871
+ * resources it imports, `cdkd drift --accept` rewrites the properties an
32872
+ * attribute may be built from, `cdkd rollback`'s replacement arm rebuilds a
32873
+ * record with the attributes a FRESH create returned, and the rest are listed
32874
+ * with the field.
32875
+ * Dropping returns those keys to pre-#2740 behaviour until the next deploy
32876
+ * recomputes the record — a row where the diff can resolve them, the ordinary
32877
+ * whole-section suppression where it cannot — which is what keeps a
32878
+ * resource-side repair out of the list above.
32879
+ *
32880
+ * The rule is flat and it is meant to be: EVERY writer that rebuilds state
32881
+ * outside a deploy drops the record, the partial-destroy snapshot being the
32882
+ * one enumerated exception — the full list lives with the field, in
32883
+ * `StackState.skippedOutputs`. Three per-writer arguments for carrying it were
32884
+ * written during review, which is the reason for the flat rule rather than an
32885
+ * aside: two were shown wrong (substituting a value repairs an output whose
32886
+ * enclosing intrinsic was choking on what was there; scrubbing a plaintext
32887
+ * back to its expression rewrites a string outputs read verbatim) and the
32888
+ * third — deleting a property EXPOSING an attribute of the same name — could
32889
+ * not be settled either way, which argues for flatness at least as strongly.
32890
+ *
32891
+ * ## Where the digest must be taken from
32892
+ *
32893
+ * The template AS HANDED to the engine / to `computeStackDiff` — before
32894
+ * parameter binding, condition evaluation or any output resolution. The
32895
+ * INVARIANT, and the reason both callers `structuredClone` rather than pass
32896
+ * their live object: **no resolution may be visible to the digest, on either
32897
+ * side, and both sides must take it at the same point of their own flow.**
32898
+ * Two independent reasons, neither of which depends on any particular
32899
+ * resolver doing the writing: a digest that could see a resolved value would
32900
+ * (1) differ between the two sides, which computes a record the diff can
32901
+ * never re-derive, and (2) fingerprint a secret into `state.json`, since a
32902
+ * resolved value can be a decrypted one.
32903
+ *
32904
+ * The clone is therefore NOT dead weight even while no resolver mutates its
32905
+ * input. It used to be load-bearing against a live rewrite — `resolveSub`
32906
+ * substituted into the caller's two-argument `Fn::Sub` variable map until
32907
+ * go-to-k/cdkd#2764 gave it a fresh object — and the protection was never
32908
+ * derived from that call site: it is derived from the invariant above, which
32909
+ * a future in-place optimisation anywhere in either flow would silently
32910
+ * violate. `tests/unit/cli/diff-recursive-skipped-outputs.test.ts` pins the
32911
+ * current no-mutation contract from the other direction (the template handed
32912
+ * to `computeStackDiff` comes back byte-identical), so reintroducing a
32913
+ * rewrite reds a test rather than moving a digest;
32914
+ * `tests/unit/deployment/deploy-engine-skipped-outputs.test.ts` INJECTS a
32915
+ * rewrite through its resolver mock and requires the recorded digest to equal
32916
+ * a fresh parse's, which pins the ordering itself.
32917
+ *
32918
+ * Given that contract the hash covers template text only. It is over
32919
+ * canonical JSON (object keys sorted at every level), so a template
32920
+ * re-serialised with keys in another order digests identically.
32921
+ *
32922
+ * ## What the diff does with a binding record
32923
+ *
32924
+ * It previews the key as ABSENT — no row — because that is exactly what the
32925
+ * deploy will leave in state. It does NOT flag the section as failed: a
32926
+ * sibling output that genuinely changed still renders, and `--fail` still
32927
+ * exits 1 for it. A record whose digest no longer matches is simply ignored:
32928
+ * the output is previewed under the ordinary rules again (usually an `ADD`;
32929
+ * an intrinsic `Export.Name` the diff still cannot resolve keeps omitting the
32930
+ * section, as before), the next deploy re-decides it —
32931
+ * publishing it if the repair took, or recording it again under the new
32932
+ * digest — and the diff follows that decision.
32933
+ */
32934
+ /**
32935
+ * JSON with object keys sorted at every level. Arrays keep their order (an
32936
+ * `Fn::Join` list is positional). `undefined` members are dropped, as
32937
+ * `JSON.stringify` drops them, so a parsed template and its in-memory twin
32938
+ * digest identically.
32939
+ */
32940
+ function canonicalJson(value) {
32941
+ return JSON.stringify(value, (_key, v) => {
32942
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) {
32943
+ const sorted = Object.create(null);
32944
+ for (const k of Object.keys(v).sort()) sorted[k] = v[k];
32945
+ return sorted;
32946
+ }
32947
+ return v;
32948
+ });
32949
+ }
32950
+ /** The constant a `NoEcho` `Default` is hashed as. Its text is irrelevant; that
32951
+ * it is CONSTANT is the point. */
32952
+ const NO_ECHO_DEFAULT_MASK = "<cdkd:noecho-default>";
32953
+ /**
32954
+ * `Parameters` with every `NoEcho: true` parameter's `Default` replaced by a
32955
+ * constant, for hashing only.
32956
+ *
32957
+ * The pre-resolution snapshot already keeps RESOLVED secret values out of the
32958
+ * digest. An authored `Default` under `NoEcho: true` is not one of those — it
32959
+ * is template text, and it went into the hash. With the rest of the
32960
+ * non-`Resources` sections known, a reader of `state.json` holds a confirm
32961
+ * ORACLE for a low-entropy default: guess it, recompute, compare.
32962
+ *
32963
+ * This does NOT claim the value is otherwise absent from state — a parameter a
32964
+ * resource reads can persist its RESOLVED default in that resource's
32965
+ * properties, and parameter `NoEcho` is outside the state-redaction model
32966
+ * either way. What it claims is narrower and still worth having: this record
32967
+ * must not ADD an oracle of its own for the case where the value is not
32968
+ * already there.
32969
+ *
32970
+ * The cost, stated rather than hidden: changing ONLY such a default no longer
32971
+ * moves the digest, so the record keeps binding through that edit until the
32972
+ * next deploy re-resolves the output. That is the same bounded staleness the
32973
+ * documented blind spots carry.
32974
+ *
32975
+ * Everything else about the parameter is still hashed — its `Type`, its
32976
+ * `AllowedValues`, the `NoEcho` flag itself — so a parameter appearing,
32977
+ * disappearing or changing shape still un-binds. Unmatched parameter content
32978
+ * hashes exactly as before: the map is rebuilt, but the canonicaliser sorts
32979
+ * keys and ignores prototypes, so a template with no `NoEcho` default gets the
32980
+ * digest it always had.
32981
+ */
32982
+ function withNoEchoDefaultsMasked(parameters) {
32983
+ if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) return parameters;
32984
+ const masked = Object.create(null);
32985
+ for (const [name, decl] of Object.entries(parameters)) {
32986
+ const body = decl;
32987
+ masked[name] = typeof body === "object" && body !== null && body["NoEcho"] === true && "Default" in body ? {
32988
+ ...body,
32989
+ Default: NO_ECHO_DEFAULT_MASK
32990
+ } : decl;
32991
+ }
32992
+ return masked;
32993
+ }
32994
+ /**
32995
+ * The digest recorded in `StackState.skippedOutputs[outputKey]` by the deploy
32996
+ * and recomputed by the diff — see the module doc for what it covers. Both
32997
+ * sides call THIS function, so the coverage is spelled once.
32998
+ *
32999
+ * `template` must be the pre-resolution snapshot the module doc describes.
33000
+ */
33001
+ function skippedOutputDigest(template, outputKey) {
33002
+ const sections = Object.create(null);
33003
+ for (const [section, body] of Object.entries(template)) {
33004
+ if (section === "Resources" || section === "Outputs") continue;
33005
+ sections[section] = section === "Parameters" ? withNoEchoDefaultsMasked(body) : body;
33006
+ }
33007
+ const entry = template.Outputs?.[outputKey];
33008
+ return createHash("sha256").update(canonicalJson({
33009
+ output: entry,
33010
+ template: sections
33011
+ })).digest("hex");
33012
+ }
33013
+ /**
33014
+ * The record a deploy writes for the bag `resolveOutputs` produced: every key
33015
+ * whose value is `undefined`, mapped to its digest. Two arms write that value:
33016
+ * the default arm of `handleOutputResolutionFailure` (the resolver THREW —
33017
+ * warned, and refused under `--strict-getatt`), and a resolver that returned
33018
+ * `undefined` outright without throwing (an attribute it could construct
33019
+ * nothing for, `constructAttribute`'s empty arm — no warn, no strict refusal).
33020
+ * Both leave the key out of a re-resolved bag (the no-change path may keep a
33021
+ * previous value under it, see the module doc), and that path's
33022
+ * `resolutionFailed` treats both the same, so this record does too: the
33023
+ * question it answers is "will the next deploy leave this key absent?", and
33024
+ * the answer is yes either way. `undefined` (the field OMITTED) when nothing
33025
+ * was skipped, so a record that carries the field says at least one output
33026
+ * was skipped.
33027
+ *
33028
+ * `template` must be the pre-resolution snapshot (see the module doc).
33029
+ */
33030
+ function collectSkippedOutputs(template, resolvedOutputs) {
33031
+ let record;
33032
+ for (const [outputKey, value] of Object.entries(resolvedOutputs)) {
33033
+ if (value !== void 0) continue;
33034
+ record ??= Object.create(null);
33035
+ record[outputKey] = skippedOutputDigest(template, outputKey);
33036
+ }
33037
+ return record;
33038
+ }
33039
+ /**
33040
+ * Every resource logical id an output entry could reference, from `Ref`,
33041
+ * either `Fn::GetAtt` spelling, and an `Fn::Sub` placeholder, walked to any
33042
+ * depth over the whole entry (`Value`, `Export.Name` and anything else it
33043
+ * declares).
33044
+ *
33045
+ * A deliberate SUPERSET: an `Fn::Sub` placeholder naming a template
33046
+ * PARAMETER, or a `Ref` to one, lands here too, since telling the two apart
33047
+ * needs the `Parameters` section and the answer only ever widens the set. The
33048
+ * one consumer intersects it with the ids that actually have a pending
33049
+ * RESOURCE change, so a name that is not a resource cannot match, and a
33050
+ * parameter deliberately named after a changed resource merely un-binds one
33051
+ * record — the direction that previews a row rather than hiding one. Pseudo
33052
+ * parameters (`AWS::…`) and the `${!Literal}` escape are dropped.
33053
+ */
33054
+ function referencedLogicalIds(entry) {
33055
+ const ids = /* @__PURE__ */ new Set();
33056
+ const addName = (name) => {
33057
+ const logicalId = name.split(".")[0];
33058
+ if (logicalId !== void 0 && logicalId !== "" && !logicalId.startsWith("AWS::")) ids.add(logicalId);
33059
+ };
33060
+ const walk = (value) => {
33061
+ if (Array.isArray(value)) {
33062
+ for (const item of value) walk(item);
33063
+ return;
33064
+ }
33065
+ if (value === null || typeof value !== "object") return;
33066
+ const obj = value;
33067
+ if (typeof obj["Ref"] === "string") addName(obj["Ref"]);
33068
+ const getAtt = obj["Fn::GetAtt"];
33069
+ if (typeof getAtt === "string") addName(getAtt);
33070
+ else if (Array.isArray(getAtt) && typeof getAtt[0] === "string") addName(getAtt[0]);
33071
+ for (const [key, nested] of Object.entries(obj)) {
33072
+ if (key === "Fn::Sub") {
33073
+ walkSub(nested);
33074
+ continue;
33075
+ }
33076
+ walk(nested);
33077
+ }
33078
+ };
33079
+ /**
33080
+ * `Fn::Sub` in both spellings. Handled here rather than by the generic walk
33081
+ * because the two-argument form's second element is a VARIABLE MAP, not a
33082
+ * template fragment: its KEYS are variable names, so letting the generic
33083
+ * walk see `{ Ref: 'Unrelated' }` there would read a variable literally
33084
+ * named `Ref` as a `Ref` intrinsic. The map's VALUES are walked, since the
33085
+ * resolver resolves them.
33086
+ */
33087
+ const walkSub = (sub) => {
33088
+ const isPair = Array.isArray(sub);
33089
+ const subTemplate = typeof sub === "string" ? sub : isPair ? sub[0] : void 0;
33090
+ const variableMap = isPair && sub[1] !== null && typeof sub[1] === "object" ? sub[1] : void 0;
33091
+ if (typeof subTemplate === "string") {
33092
+ const declaredVars = new Set(variableMap === void 0 ? [] : Object.keys(variableMap));
33093
+ for (const [, placeholder] of subTemplate.matchAll(/\$\{([^}]*)\}/g)) {
33094
+ if (placeholder === void 0 || placeholder.startsWith("!")) continue;
33095
+ if (declaredVars.has(placeholder)) continue;
33096
+ addName(placeholder);
33097
+ }
33098
+ }
33099
+ if (isPair) for (const [index, element] of sub.entries()) {
33100
+ if (index === 1) {
33101
+ if (variableMap !== void 0) for (const declared of Object.values(variableMap)) walk(declared);
33102
+ continue;
33103
+ }
33104
+ walk(element);
33105
+ }
33106
+ else walk(sub);
33107
+ };
33108
+ walk(entry);
33109
+ return ids;
33110
+ }
33111
+ /**
33112
+ * The keys of a `StackState.skippedOutputs` record that still BIND against
33113
+ * today's template: recorded digest equal to the digest of the same key over
33114
+ * `pristineTemplate`, which must be the pre-resolution snapshot the module doc
33115
+ * describes. A key the template no longer declares is skipped outright — the
33116
+ * record is inert for it, and the next deploy clears it. The diff passes the
33117
+ * result to `resolveTemplateOutputs`, which adds the one check only state can
33118
+ * answer — that the key is still absent from the stored bag.
33119
+ *
33120
+ * `changedLogicalIds` is the second half of the binding rule, and the one the
33121
+ * digest structurally cannot carry (see the module doc's `Resources` bullet):
33122
+ * the logical ids this run's resource diff reports as changing. An output
33123
+ * whose entry references one of them does NOT bind, because the deploy that
33124
+ * follows takes the changed-resources path, re-resolves every output, and may
33125
+ * publish the very key the record would have hidden. Omitted by a caller with
33126
+ * no resource diff in hand — every such caller today is a test asserting the
33127
+ * digest half alone.
33128
+ */
33129
+ function bindingSkippedOutputs(pristineTemplate, record, changedLogicalIds) {
33130
+ const binding = /* @__PURE__ */ new Set();
33131
+ if (record === null || record === void 0) return binding;
33132
+ const declared = pristineTemplate.Outputs ?? {};
33133
+ for (const [outputKey, digest] of Object.entries(record)) {
33134
+ if (!Object.prototype.hasOwnProperty.call(declared, outputKey)) continue;
33135
+ const entry = declared[outputKey];
33136
+ if (digest !== skippedOutputDigest(pristineTemplate, outputKey)) continue;
33137
+ if (changedLogicalIds !== void 0 && [...referencedLogicalIds(entry)].some((id) => changedLogicalIds.has(id))) continue;
33138
+ binding.add(outputKey);
33139
+ }
33140
+ return binding;
33141
+ }
33142
+ /**
33143
+ * Whether two records — either possibly absent — describe the same skipped
33144
+ * set with the same digests. The no-change deploy path persists on a
33145
+ * difference here: it is the ONLY save trigger for this field on that path,
33146
+ * because a skipped output switches the outputs-changed trigger off.
33147
+ */
33148
+ function skippedOutputsEqual(a, b) {
33149
+ const aKeys = a === null || a === void 0 ? [] : Object.keys(a);
33150
+ const bKeys = b === null || b === void 0 ? [] : Object.keys(b);
33151
+ if (aKeys.length !== bKeys.length) return false;
33152
+ return aKeys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && a?.[k] === b?.[k]);
33153
+ }
33154
+
32685
33155
  //#endregion
32686
33156
  //#region src/analyzer/implicit-delete-deps.ts
32687
33157
  /**
@@ -35198,8 +35668,17 @@ var DeployEngine = class {
35198
35668
  * secret collapse onto whichever expression was recorded last, exactly as two
35199
35669
  * resource properties did before #1904. Captured in `resolveOutputs` at the
35200
35670
  * same point `outputSecrets` is accumulated. Reset per `deploy()`.
35671
+ *
35672
+ * Null-prototype for the same reason as the outputs bag `resolveOutputs`
35673
+ * builds two lines away: both are keyed by TEMPLATE-CONTROLLED output names,
35674
+ * so an output called `__proto__` would replace this bag's prototype and a
35675
+ * later lookup of a name it never stored would inherit from it. Only the
35676
+ * RESET in `deploy()` is pinned — reverting this initialiser alone survives
35677
+ * the suite, measured, because `deploy()` replaces the bag before anything
35678
+ * writes to it, so the initialiser matches the reset rather than standing as
35679
+ * a second guard.
35201
35680
  */
35202
- outputsTemplateSource = {};
35681
+ outputsTemplateSource = Object.create(null);
35203
35682
  /**
35204
35683
  * The export aliases the last `resolveOutputs` pass WROTE into its bag
35205
35684
  * (issue #2193) — exactly the keys `outputs[exportName] = value` landed on,
@@ -35212,6 +35691,18 @@ var DeployEngine = class {
35212
35691
  */
35213
35692
  resolvedExportNames = [];
35214
35693
  /**
35694
+ * The outputs the last `resolveOutputs` pass could NOT resolve and SKIPPED
35695
+ * (the resolver threw under the default arm of
35696
+ * `handleOutputResolutionFailure`, or returned `undefined` outright — see
35697
+ * `collectSkippedOutputs`), each mapped to the
35698
+ * digest `cdkd diff` compares against (issue #2740, `StackState.
35699
+ * skippedOutputs`). `undefined` when nothing was skipped, so the saves that
35700
+ * persist the resolved bag spread it as-is and the field stays omitted. Same
35701
+ * lifetime rule as `resolvedExportNames`: reset at the top of every
35702
+ * `resolveOutputs`, meaningful only right after that call returns.
35703
+ */
35704
+ skippedOutputs;
35705
+ /**
35215
35706
  * Whether {@link outputsTemplateSource} may be used to POSITION the outputs
35216
35707
  * redaction. False once an outputs pass threw partway: the post-loop
35217
35708
  * name pass never ran, so the bag holds only the alias keys written before
@@ -35298,8 +35789,9 @@ var DeployEngine = class {
35298
35789
  this.perResourceTemplateProps = /* @__PURE__ */ new Map();
35299
35790
  this.attemptedResolvedProps = /* @__PURE__ */ new Map();
35300
35791
  this.outputSecrets = /* @__PURE__ */ new Map();
35301
- this.outputsTemplateSource = {};
35792
+ this.outputsTemplateSource = Object.create(null);
35302
35793
  this.outputsSourceUsable = true;
35794
+ this.skippedOutputs = void 0;
35303
35795
  this.retainedOldOnReplacement = /* @__PURE__ */ new Set();
35304
35796
  this.resolver.resetPhysicalIdFallbackCount();
35305
35797
  return withStackName(stackName, () => this.doDeploy(stackName, template));
@@ -35783,6 +36275,10 @@ var DeployEngine = class {
35783
36275
  } catch {}
35784
36276
  this.kickOffAutoRefreshObservedProperties(currentState.resources);
35785
36277
  this.logger.debug(`Template has ${Object.keys(template.Resources || {}).length} resources`);
36278
+ const outputsDigestSource = structuredClone({
36279
+ ...template,
36280
+ Resources: {}
36281
+ });
35786
36282
  const parameterValues = await this.resolver.resolveParameters(template, this.options.parameters, { ...this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0 && { inheritedSecrets: this.options.inheritedSecrets } });
35787
36283
  this.logger.debug(`Resolved ${Object.keys(parameterValues).length} parameters: ${Object.keys(parameterValues).join(", ")}`);
35788
36284
  const context = this.buildResolverContext({
@@ -35826,7 +36322,7 @@ var DeployEngine = class {
35826
36322
  this.logger.info("No changes detected. Stack is up to date.");
35827
36323
  let persistedOutputs = currentState.outputs ?? {};
35828
36324
  if (!this.options.dryRun) {
35829
- const resolvedOutputs = this.redactOutputs(await this.resolveOutputs(effectiveTemplate, currentState.resources, stackName, parameterValues, conditions));
36325
+ const resolvedOutputs = this.redactOutputs(await this.resolveOutputs(effectiveTemplate, currentState.resources, stackName, outputsDigestSource, parameterValues, conditions));
35830
36326
  const resolutionFailed = Object.values(resolvedOutputs).some((v) => v === void 0);
35831
36327
  const outputsChanged = !resolutionFailed && !outputMapsEqual(persistedOutputs, resolvedOutputs);
35832
36328
  const currentEffectiveExports = new Set(importableOutputKeys(currentState));
@@ -35835,7 +36331,8 @@ var DeployEngine = class {
35835
36331
  if (resolutionFailed && !outputMapsEqual(persistedOutputs, resolvedOutputs)) this.logger.warn("Outputs changed but one or more could not be resolved; keeping the previously persisted outputs. A downstream Fn::ImportValue may fail until the next deploy.");
35836
36332
  const observedRefresh = this.observedCaptureTasks.size > 0;
35837
36333
  if (observedRefresh) await this.drainObservedCaptures(currentState.resources);
35838
- if (observedRefresh || outputsChanged || exportSetChanged) try {
36334
+ const skippedOutputsChanged = !skippedOutputsEqual(currentState.skippedOutputs, this.skippedOutputs);
36335
+ if (observedRefresh || outputsChanged || exportSetChanged || skippedOutputsChanged) try {
35839
36336
  const refreshedState = {
35840
36337
  version: 9,
35841
36338
  region: this.stackRegion,
@@ -35843,6 +36340,7 @@ var DeployEngine = class {
35843
36340
  resources: currentState.resources,
35844
36341
  outputs: outputsChanged ? resolvedOutputs : persistedOutputs,
35845
36342
  ...resolutionFailed ? exportNamesCarriedFrom(currentState) : { exportNames: [...this.resolvedExportNames] },
36343
+ ...this.skippedOutputs && { skippedOutputs: { ...this.skippedOutputs } },
35846
36344
  ...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
35847
36345
  lastModified: Date.now()
35848
36346
  };
@@ -35855,7 +36353,8 @@ var DeployEngine = class {
35855
36353
  if (outputsChanged) this.logger.info("Persisted Outputs-only change (no resource diff).");
35856
36354
  else this.logger.debug("Persisted export-set change (no outputs-value diff, no-change path, #2193)");
35857
36355
  if (this.exportIndexStore) await this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs(refreshedState));
35858
- } else this.logger.debug("Persisted refreshed observedProperties (no-change path)");
36356
+ } else if (observedRefresh) this.logger.debug("Persisted refreshed observedProperties (no-change path)");
36357
+ else this.logger.debug("Persisted skipped-outputs record (no outputs-value diff, no-change path, #2740)");
35859
36358
  } catch (saveError) {
35860
36359
  this.logger.warn(`Failed to persist no-change state update: ${saveError instanceof Error ? saveError.message : String(saveError)} — drift baseline / outputs will be re-resolved on next deploy.`);
35861
36360
  }
@@ -35897,7 +36396,7 @@ var DeployEngine = class {
35897
36396
  current: 0,
35898
36397
  total: createChanges.length + updateChanges.length + deleteChanges.length
35899
36398
  };
35900
- const { state: newState, actualCounts } = await this.executeDeployment(effectiveTemplate, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress, migrationPending);
36399
+ const { state: newState, actualCounts } = await this.executeDeployment(effectiveTemplate, currentState, changes, dag, executionLevels, stackName, outputsDigestSource, parameterValues, conditions, currentEtag, progress, migrationPending);
35901
36400
  await this.drainObservedCaptures(newState.resources);
35902
36401
  const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, this.withParentInfo(newState));
35903
36402
  this.logger.debug(`State saved (ETag: ${newEtag})`);
@@ -35939,7 +36438,7 @@ var DeployEngine = class {
35939
36438
  * - DELETE follows reverse dependency order (a node starts as soon as all
35940
36439
  * resources that depend ON it have finished deleting)
35941
36440
  */
35942
- async executeDeployment(template, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress, migrationPending = false) {
36441
+ async executeDeployment(template, currentState, changes, dag, executionLevels, stackName, outputsDigestSource, parameterValues, conditions, currentEtag, progress, migrationPending = false) {
35943
36442
  const concurrency = this.options.concurrency;
35944
36443
  const newResources = { ...currentState.resources };
35945
36444
  const actualCounts = {
@@ -35965,6 +36464,7 @@ var DeployEngine = class {
35965
36464
  resources: newResources,
35966
36465
  outputs: currentState.outputs,
35967
36466
  ...exportNamesCarriedFrom(currentState),
36467
+ ...skippedOutputsCarriedFrom(currentState),
35968
36468
  ...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
35969
36469
  lastModified: Date.now()
35970
36470
  };
@@ -36097,6 +36597,7 @@ var DeployEngine = class {
36097
36597
  resources: newResources,
36098
36598
  outputs: currentState.outputs,
36099
36599
  ...exportNamesCarriedFrom(currentState),
36600
+ ...skippedOutputsCarriedFrom(currentState),
36100
36601
  ...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
36101
36602
  lastModified: Date.now()
36102
36603
  };
@@ -36133,6 +36634,7 @@ var DeployEngine = class {
36133
36634
  resources: newResources,
36134
36635
  outputs: currentState.outputs,
36135
36636
  ...exportNamesCarriedFrom(currentState),
36637
+ ...skippedOutputsCarriedFrom(currentState),
36136
36638
  ...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
36137
36639
  lastModified: Date.now()
36138
36640
  };
@@ -36150,6 +36652,7 @@ var DeployEngine = class {
36150
36652
  resources: newResources,
36151
36653
  outputs: currentState.outputs,
36152
36654
  ...exportNamesCarriedFrom(currentState),
36655
+ ...skippedOutputsCarriedFrom(currentState),
36153
36656
  ...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
36154
36657
  lastModified: Date.now()
36155
36658
  };
@@ -36164,7 +36667,7 @@ var DeployEngine = class {
36164
36667
  }
36165
36668
  let outputs;
36166
36669
  try {
36167
- outputs = await this.resolveOutputs(template, newResources, stackName, parameterValues, conditions);
36670
+ outputs = await this.resolveOutputs(template, newResources, stackName, outputsDigestSource, parameterValues, conditions);
36168
36671
  const resolvedOutputsBeforeRedaction = outputs;
36169
36672
  outputs = this.redactOutputs(outputs);
36170
36673
  this.rememberRecoverableMaskedOutputs(stackName, resolvedOutputsBeforeRedaction, outputs);
@@ -36181,6 +36684,7 @@ var DeployEngine = class {
36181
36684
  resources: newResources,
36182
36685
  outputs,
36183
36686
  exportNames: [...this.resolvedExportNames],
36687
+ ...this.skippedOutputs && { skippedOutputs: { ...this.skippedOutputs } },
36184
36688
  ...this.recordedImports.length > 0 && { imports: [...this.recordedImports] },
36185
36689
  ...this.recordedOutputReads.length > 0 && { outputReads: [...this.recordedOutputReads] },
36186
36690
  lastModified: Date.now()
@@ -36219,6 +36723,7 @@ var DeployEngine = class {
36219
36723
  resources: newResources,
36220
36724
  outputs: currentState.outputs,
36221
36725
  ...exportNamesCarriedFrom(currentState),
36726
+ ...skippedOutputsCarriedFrom(currentState),
36222
36727
  ...crossStackReadsForPartialSave(currentState, this.recordedImports, this.recordedOutputReads),
36223
36728
  lastModified: Date.now()
36224
36729
  });
@@ -37399,10 +37904,10 @@ var DeployEngine = class {
37399
37904
  * rather than whatever the declaration order happened to have recorded by
37400
37905
  * then (issue #1919).
37401
37906
  */
37402
- async resolveOutputs(template, resources, stackName, parameterValues, conditions) {
37907
+ async resolveOutputs(template, resources, stackName, digestSource, parameterValues, conditions) {
37403
37908
  this.resolvedExportNames = [];
37404
37909
  if (!template.Outputs) return {};
37405
- const outputs = {};
37910
+ const outputs = Object.create(null);
37406
37911
  const context = this.buildResolverContext({
37407
37912
  template,
37408
37913
  resources,
@@ -37467,6 +37972,7 @@ var DeployEngine = class {
37467
37972
  if (!publishedOutputNames.has(outputKey)) continue;
37468
37973
  this.outputsTemplateSource[outputKey] = output.Value;
37469
37974
  }
37975
+ this.skippedOutputs = collectSkippedOutputs(digestSource, outputs);
37470
37976
  return markSameGenerationBag(outputs);
37471
37977
  }
37472
37978
  buildDisplayOutputs(template, resolvedOutputs) {
@@ -37481,5 +37987,5 @@ var DeployEngine = class {
37481
37987
  };
37482
37988
 
37483
37989
  //#endregion
37484
- export { createMaskedRetryLogger as $, runDockerStreaming as $n, StackTerminationProtectionError as $r, carriesSecretMask as $t, WARM_THROUGHPUT_MEMBERS as A, rewriteTemplateAssetReferences as An, clearBucketRegionCache as Ar, replayWarn as At, yellow as B, validateContainerRepoName as Bn, DependencyError as Br, s3BucketWebsiteUrl as Bt, unsupportedFinalSnapshotError as C, shouldRetainResource as Cn, displaySafe as Cr, normalizeAwsTagsToCfn as Ct, isStatefulRecreateTargetForReplace as D, buildAssetRedirectMap as Dn, derivePartitionAndUrlSuffix as Dr, configBooleanRefusal as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, WorkGraph as En, canonicalizeRegion as Er, coerceCfnBoolean as Et, bold as F, getBootstrapMarkerKey as Fn, setAwsClients as Fr, producerRegionsFromState as Ft, secretBearingStateKeyWarning as G, describeDockerExecFailure as Gn, LocalStartServiceError as Gr, findSilentDropProperties as Gt, collectPublishedOutputNames as H, describeAwsFailure as Hn, DynamicReferenceRegionAmbiguousError as Hr, DiffCalculator as Ht, cyan as I, isCrossRegionRedirect as In, AssetError as Ir, s3BucketArn as It, IAMRoleProvider as J, formatDockerLoginError as Jn, PartialFailureError as Jr, DagBuilder as Jt, stateKeySecretExposure as K, describeDockerFailure as Kn, LockError as Kr, describeTypeWithThrottleRetry as Kt, gray as L, parseBootstrapMarker as Ln, CdkdError as Lr, s3BucketDomainName as Lt, isWarmThroughputDecrease as M, BOOTSTRAP_MARKER_PREFIX as Mn, AwsClients as Mr, requireConfigObject as Mt, toFiniteNumber as N, assertAssetBucketRegion as Nn, getAwsClients as Nr, requireConfigString as Nt, isStatefulRecreateTargetSync as O, createAssetRedirectResolver as On, AssemblyReader as Or, configStringRefusal as Ot, formatResourceLine as P, ensureAssetStorage as Pn, resetAwsClients as Pr, classifyReplaySecretRegion as Pt, wouldReturnToSdkProvider as Q, runDockerForeground as Qn, StackHasActiveImportsError as Qr, TEMPLATE_SOURCED_RULES as Qt, green as R, readBootstrapMarkerBody as Rn, ConfigError as Rr, s3BucketDualStackDomainName as Rt, refusesFinalSnapshot as S, importableOutputs as Sn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Sr, WAFv2WebACLProvider as St, extractDeploymentEventError as T, stringifyValue as Tn, PARTITION_TABLE as Tr, assertRegionMatch as Tt, exportAliasCollisionScrubWarning as U, buildDockerImage as Un, IntrinsicResolutionRefusalError as Ur, INTRINSIC_KEYS as Ut, collectDeclaredOutputNames as V, buildDenyExternalAccessPolicy as Vn, DeployCancelledError as Vr, applyRoleArnIfSet as Vt, isExportAliasCollision as W, describeDockerCapturedOutput as Wn, LocalInvokeBuildError as Wr, findActionableSilentDrops as Wt, clearOnUpdateRemoval as X, partitionSensitiveEnv as Xn, ResourceTimeoutError as Xr, STATE_SOURCED_CROSS_GENERATION_RULES as Xt, collectInlinePolicyNamesManagedBySiblings as Y, getDockerCmd as Yn, ProvisioningError as Yr, TemplateParser as Yt, ProviderRegistry as Z, redactDockerArgvValues as Zn, ResourceUpdateNotSupportedError as Zr, STATE_SOURCED_READBACK_RULES as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, shellQuote as _n, CFN_TEMPLATE_BODY_LIMIT as _r, coerceParameterTypedValue as _t, DeploymentEventsStore as a, withErrorHandling as ai, maskSecretsInText as an, synthesisStatusMessage as ar, isInterruptedWaitError as at, createPreDeleteFinalSnapshot as b, exportNamesCarriedFrom as bn, findLargeInlineResources as br, parameterTypeMayLoseSecretIdentity as bt, replayFailedOperations as c, isThrottlingError as ci, redactSecretsForState as cn, resolveApp as cr, slowCcOperationTimeoutMs as ct, updatePartialReason as d, markRedactedCause as di, S3StateBackend as dn, resolveSkipPrefix as dr, deleteSkipReason as dt, StateError as ei, createSecretMasker as en, escapeRegExp$1 as er, maskDeep as et, withResourceDeadline as f, retryClassificationText as fi, rebuildClientForBucketRegion as fn, resolveStateBucketWithDefault as fr, disableInstanceApiTermination as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, forceQuitRecoveryClause as gn, warnDeprecatedNoPrefixCliFlag as gr, cfnRefValueFromPhysicalId as gt, computeImplicitDeleteEdges as h, buildLockContentionMessage as hn, stateBucketExistenceConfirmed as hr, carriesDynamicReference as ht, DeploymentEventsReader as i, normalizeAwsError as ii, maskSecretsInError as in, Synthesizer as ir, interruptWatchListenerCount as it, coerceWarmThroughput as j, AssetModeResolver as jn, resolveBucketRegion as jr, requireConfigArray as jt, renderStatefulReason as k, loadPublishableAssetManifest as kn, processStackMessages as kr, readConfigString as kt, replayRollback as l, isTransientServerError as li, scrubResourceRecord as ln, resolveAutoAssetStorage as lr, UNSPECIFIED_SKIP_REASON as lt, IMPLICIT_DELETE_DEPENDENCIES as m, buildForceUnlockCommand as mn, resolveUseCdkBootstrapAssets as mr, IntrinsicFunctionResolver as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, formatError as ni, errorCauseChain as nn, AssetManifestLoader as nr, beginCommandInterruptScope as nt, planFailedOps as o, isMarkedNonRetryable as oi, recordMaskOnlyValue as on, getDefaultStateBucketName as or, startInterruptWatch as ot, maskingRetryLogger as p, __exportAll as pi, UNRENDERABLE as pn, resolveStateBucketWithDefaultAndSource as pr, isTerminationProtectionPropagationError as pt, getCurrentResourceSecrets as q, dockerSpawnEnvWithSensitive as qn, NestedStackChildDirectDestroyError as qr, withRetry as qt, DeployEngine as r, isCdkdError as ri, isSingleDynamicReferenceToken as rn, getDockerImageBySourceHash as rr, endCommandInterruptScope as rt, planRollback as s, isRetryableTransientError as si, recoverMaskedOutput as sn, getLegacyStateBucketName as sr, CloudControlProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, SynthesisError as ti, dynamicReferenceTokens as tn, stripControlChars as tr, maskerOrIdentity as tt, updatePartialMessage as u, markNonRetryable as ui, LockManager as un, resolveCaptureObservedState as ur, deleteIndeterminateGuards as ut, buildFinalSnapshotIdentifier as v, CUSTOM_RESOURCE_RESPONSE_PREFIX as vn, CFN_TEMPLATE_URL_LIMIT as vr, getAccountInfo as vt, makeCanonicalizePropertiesFn as w, AssetPublisher as wn, expectedOwnerParam as wr, resolveExplicitPhysicalId as wt, isFinalSnapshotError as x, importableOutputKeys as xn, uploadCfnTemplate as xr, refStateLookupFromResource as xt, ccRoutedFinalSnapshotError as y, DEFAULT_STATE_PREFIX as yn, MIGRATE_TMP_PREFIX as yr, isUnboundTemplateParameter as yt, red as z, validateAssetBucketName as zn, CrossAccountSecretRefusalError as zr, s3BucketRegionalDomainName as zt };
37485
- //# sourceMappingURL=deploy-engine-Cxt3gn8N.js.map
37990
+ export { ProviderRegistry as $, redactDockerArgvValues as $n, ResourceUpdateNotSupportedError as $r, STATE_SOURCED_READBACK_RULES as $t, renderStatefulReason as A, createAssetRedirectResolver as An, AssemblyReader as Ar, configStringRefusal as At, red as B, readBootstrapMarkerBody as Bn, ConfigError as Br, s3BucketDualStackDomainName as Bt, refusesFinalSnapshot as C, importableOutputKeys as Cn, uploadCfnTemplate as Cr, refStateLookupFromResource as Ct, MULTI_REGION_RECREATE_BLOCKED_TYPES as D, stringifyValue as Dn, PARTITION_TABLE as Dr, assertRegionMatch as Dt, extractDeploymentEventError as E, AssetPublisher as En, expectedOwnerParam as Er, resolveExplicitPhysicalId as Et, formatResourceLine as F, assertAssetBucketRegion as Fn, getAwsClients as Fr, requireConfigString as Ft, isExportAliasCollision as G, buildDockerImage as Gn, IntrinsicResolutionRefusalError as Gr, INTRINSIC_KEYS as Gt, collectDeclaredOutputNames as H, validateContainerRepoName as Hn, DependencyError as Hr, s3BucketWebsiteUrl as Ht, bold as I, ensureAssetStorage as In, resetAwsClients as Ir, classifyReplaySecretRegion as It, stateKeySecretExposure as J, describeDockerFailure as Jn, LockError as Jr, describeTypeWithThrottleRetry as Jt, secretBearingStateKeyWarning as K, describeDockerCapturedOutput as Kn, LocalInvokeBuildError as Kr, findActionableSilentDrops as Kt, cyan as L, getBootstrapMarkerKey as Ln, setAwsClients as Lr, producerRegionsFromState as Lt, coerceWarmThroughput as M, rewriteTemplateAssetReferences as Mn, clearBucketRegionCache as Mr, replayWarn as Mt, isWarmThroughputDecrease as N, AssetModeResolver as Nn, resolveBucketRegion as Nr, requireConfigArray as Nt, isStatefulRecreateTargetForReplace as O, WorkGraph as On, canonicalizeRegion as Or, coerceCfnBoolean as Ot, toFiniteNumber as P, BOOTSTRAP_MARKER_PREFIX as Pn, AwsClients as Pr, requireConfigObject as Pt, clearOnUpdateRemoval as Q, partitionSensitiveEnv as Qn, ResourceTimeoutError as Qr, STATE_SOURCED_CROSS_GENERATION_RULES as Qt, gray as R, isCrossRegionRedirect as Rn, AssetError as Rr, s3BucketArn as Rt, isFinalSnapshotError as S, exportNamesCarriedFrom as Sn, findLargeInlineResources as Sr, parameterTypeMayLoseSecretIdentity as St, makeCanonicalizePropertiesFn as T, shouldRetainResource as Tn, displaySafe as Tr, normalizeAwsTagsToCfn as Tt, collectPublishedOutputNames as U, buildDenyExternalAccessPolicy as Un, DeployCancelledError as Ur, applyRoleArnIfSet as Ut, yellow as V, validateAssetBucketName as Vn, CrossAccountSecretRefusalError as Vr, s3BucketRegionalDomainName as Vt, exportAliasCollisionScrubWarning as W, describeAwsFailure as Wn, DynamicReferenceRegionAmbiguousError as Wr, DiffCalculator as Wt, IAMRoleProvider as X, formatDockerLoginError as Xn, PartialFailureError as Xr, DagBuilder as Xt, getCurrentResourceSecrets as Y, dockerSpawnEnvWithSensitive as Yn, NestedStackChildDirectDestroyError as Yr, withRetry as Yt, collectInlinePolicyNamesManagedBySiblings as Z, getDockerCmd as Zn, ProvisioningError as Zr, TemplateParser as Zt, ATOMIC_FINAL_SNAPSHOT_TYPES as _, buildLockContentionMessage as _n, stateBucketExistenceConfirmed as _r, carriesDynamicReference as _t, DeploymentEventsStore as a, isCdkdError as ai, isSingleDynamicReferenceToken as an, getDockerImageBySourceHash as ar, endCommandInterruptScope as at, ccRoutedFinalSnapshotError as b, CUSTOM_RESOURCE_RESPONSE_PREFIX as bn, CFN_TEMPLATE_URL_LIMIT as br, getAccountInfo as bt, replayFailedOperations as c, isMarkedNonRetryable as ci, recordMaskOnlyValue as cn, getDefaultStateBucketName as cr, startInterruptWatch as ct, updatePartialReason as d, isTransientServerError as di, scrubResourceRecord as dn, resolveAutoAssetStorage as dr, UNSPECIFIED_SKIP_REASON as dt, StackHasActiveImportsError as ei, TEMPLATE_SOURCED_RULES as en, runDockerForeground as er, wouldReturnToSdkProvider as et, withResourceDeadline as f, markNonRetryable as fi, LockManager as fn, resolveCaptureObservedState as fr, deleteIndeterminateGuards as ft, bindingSkippedOutputs as g, buildForceUnlockCommand as gn, resolveUseCdkBootstrapAssets as gr, IntrinsicFunctionResolver as gt, computeImplicitDeleteEdges as h, __exportAll as hi, UNRENDERABLE as hn, resolveStateBucketWithDefaultAndSource as hr, isTerminationProtectionPropagationError as ht, DeploymentEventsReader as i, formatError as ii, errorCauseChain as in, AssetManifestLoader as ir, beginCommandInterruptScope as it, WARM_THROUGHPUT_MEMBERS as j, loadPublishableAssetManifest as jn, processStackMessages as jr, readConfigString as jt, isStatefulRecreateTargetSync as k, buildAssetRedirectMap as kn, derivePartitionAndUrlSuffix as kr, configBooleanRefusal as kt, replayRollback as l, isRetryableTransientError as li, recoverMaskedOutput as ln, getLegacyStateBucketName as lr, CloudControlProvider as lt, IMPLICIT_DELETE_DEPENDENCIES as m, retryClassificationText as mi, rebuildClientForBucketRegion as mn, resolveStateBucketWithDefault as mr, disableInstanceApiTermination as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, StateError as ni, createSecretMasker as nn, escapeRegExp$1 as nr, maskDeep as nt, planFailedOps as o, normalizeAwsError as oi, maskSecretsInError as on, Synthesizer as or, interruptWatchListenerCount as ot, maskingRetryLogger as p, markRedactedCause as pi, S3StateBackend as pn, resolveSkipPrefix as pr, deleteSkipReason as pt, secretSafeKeyDisplay as q, describeDockerExecFailure as qn, LocalStartServiceError as qr, findSilentDropProperties as qt, DeployEngine as r, SynthesisError as ri, dynamicReferenceTokens as rn, stripControlChars as rr, maskerOrIdentity as rt, planRollback as s, withErrorHandling as si, maskSecretsInText as sn, synthesisStatusMessage as sr, isInterruptedWaitError as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, StackTerminationProtectionError as ti, carriesSecretMask as tn, runDockerStreaming as tr, createMaskedRetryLogger as tt, updatePartialMessage as u, isThrottlingError as ui, redactSecretsForState as un, resolveApp as ur, slowCcOperationTimeoutMs as ut, PRE_DELETE_SNAPSHOT_TYPES as v, forceQuitRecoveryClause as vn, warnDeprecatedNoPrefixCliFlag as vr, cfnRefValueFromPhysicalId as vt, unsupportedFinalSnapshotError as w, importableOutputs as wn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as wr, WAFv2WebACLProvider as wt, createPreDeleteFinalSnapshot as x, DEFAULT_STATE_PREFIX as xn, MIGRATE_TMP_PREFIX as xr, isUnboundTemplateParameter as xt, buildFinalSnapshotIdentifier as y, shellQuote as yn, CFN_TEMPLATE_BODY_LIMIT as yr, coerceParameterTypedValue as yt, green as z, parseBootstrapMarker as zn, CdkdError as zr, s3BucketDomainName as zt };
37991
+ //# sourceMappingURL=deploy-engine-CfxcC3q3.js.map