@go-to-k/cdkd 0.283.31 → 0.283.32

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.
@@ -5560,6 +5560,27 @@ var AssetModeResolver = class {
5560
5560
  function escapeRegExp$1(value) {
5561
5561
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5562
5562
  }
5563
+ /**
5564
+ * Remove control characters from a value about to be PRINTED.
5565
+ *
5566
+ * Lives beside {@link escapeRegExp} for the reason that helper's own note
5567
+ * gives: this was spelled twice — in `src/cli/commands/diff-recursive.ts` and
5568
+ * in `src/deployment/outputs-export-alias.ts` — and copies of a
5569
+ * security-shaped helper drift. Both print the same class of string: a
5570
+ * resolved CloudFormation Output / `Export.Name`, which passed no CFn
5571
+ * validator, so it can carry ANSI escapes or bidi overrides straight into a
5572
+ * terminal or a CI log.
5573
+ *
5574
+ * C0 + DEL + C1 + the bidi marks and isolates. Apply on HUMAN-render paths
5575
+ * only: a `--json` payload is a machine interface where an export name is data
5576
+ * a consumer may match on, and mutating it there trades a correctness
5577
+ * regression for a display concern (see `stripDisplayOnlyChars`, which is the
5578
+ * narrower guard for an already-serialized payload and stays local to its
5579
+ * caller because its C0 exclusion is specific to that path).
5580
+ */
5581
+ function stripControlChars(value) {
5582
+ return value.replace(/[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "");
5583
+ }
5563
5584
 
5564
5585
  //#endregion
5565
5586
  //#region src/assets/asset-redirect.ts
@@ -15076,7 +15097,7 @@ var CloudControlProvider = class {
15076
15097
  if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
15077
15098
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
15078
15099
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
15079
- const { ASGProvider } = await import("./asg-provider-SQ0V0nFd.js").then((n) => n.n);
15100
+ const { ASGProvider } = await import("./asg-provider-CVoisQ0L.js").then((n) => n.n);
15080
15101
  return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
15081
15102
  }
15082
15103
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -20131,6 +20152,262 @@ function collectInlinePolicyNamesManagedBySiblings(targetPhysicalId, context, at
20131
20152
  return result;
20132
20153
  }
20133
20154
 
20155
+ //#endregion
20156
+ //#region src/deployment/outputs-export-alias.ts
20157
+ /**
20158
+ * Does CloudFormation suppress this output on this deploy?
20159
+ *
20160
+ * CFn does not create an output whose `Condition` evaluates false, and
20161
+ * `resolveOutputs` mirrors that (issue #1028). Unknown condition names are
20162
+ * KEPT, matching `filterResourcesByCondition` on the resource side — a
20163
+ * condition cdkd could not evaluate must not silently delete an output.
20164
+ *
20165
+ * DEPLOY-SIDE ONLY. `cdkd scrub` deliberately does not use this: see
20166
+ * {@link collectDeclaredOutputNames}.
20167
+ */
20168
+ function isOutputSuppressedByCondition(output, conditions) {
20169
+ return output.Condition !== void 0 && conditions?.[output.Condition] === false;
20170
+ }
20171
+ /**
20172
+ * The output NAMES this deploy actually publishes — every declared output minus
20173
+ * the condition-suppressed ones.
20174
+ *
20175
+ * This is the set that owns keys in BOTH bags, and the reason the answer is
20176
+ * "published" rather than "declared": a suppressed output writes no value, so
20177
+ * it must not write a position source either, and its name is free for an
20178
+ * export alias to use. Reserving names for suppressed outputs would drop a
20179
+ * WORKING export the moment an unrelated condition went false.
20180
+ *
20181
+ * Sound at deploy time because these are the SAME condition values the deploy
20182
+ * itself acted on. Not sound in scrub — see {@link collectDeclaredOutputNames}.
20183
+ */
20184
+ function collectPublishedOutputNames(outputs, conditions) {
20185
+ const names = /* @__PURE__ */ new Set();
20186
+ for (const [name, output] of Object.entries(outputs)) if (!isOutputSuppressedByCondition(output, conditions)) names.add(name);
20187
+ return names;
20188
+ }
20189
+ /**
20190
+ * Every DECLARED output name, conditions ignored — the set `cdkd scrub` tests
20191
+ * collisions against.
20192
+ *
20193
+ * Scrub must be a SUPERSET here, and the asymmetry with the deploy engine is
20194
+ * forced by what scrub can know. Its condition values are re-evaluated
20195
+ * best-effort, from template defaults only (the command takes no
20196
+ * `--parameters`), and `evaluateConditions` assumes FALSE on any evaluation
20197
+ * failure. So "suppressed" is both easy to hit spuriously and impossible to
20198
+ * confirm against the deploy that actually wrote the state.
20199
+ *
20200
+ * The two error directions are not symmetric, which is what settles the rule:
20201
+ *
20202
+ * - Judging a colliding output suppressed when the DEPLOY published it (the
20203
+ * spurious-false case above) makes scrub miss the collision, write the
20204
+ * exporting output's expression over the colliding key, and persist a
20205
+ * reference naming a DIFFERENT secret — the #1919 corruption, produced by
20206
+ * the remediation command itself.
20207
+ * - Judging it published when the deploy suppressed it costs one spurious
20208
+ * warning and one key redacted by VALUE match instead of by position. State
20209
+ * exactly what that costs, since an earlier revision understated it: the
20210
+ * value map is keyed by PLAINTEXT, so when two DISTINCT secrets resolve to
20211
+ * one value it keeps only the last, and that key can be persisted holding a
20212
+ * reference naming the OTHER secret. It is a smaller blast radius than the
20213
+ * first case (one key, and only when two secrets coincide, versus every
20214
+ * collision) but it is the same KIND of error, not a mere loss of precision.
20215
+ *
20216
+ * A wrong reference beats a lost precision bound, so scrub over-approximates.
20217
+ */
20218
+ function collectDeclaredOutputNames(outputs) {
20219
+ return new Set(Object.keys(outputs));
20220
+ }
20221
+ /**
20222
+ * Would aliasing `exportName` land on a key another output owns?
20223
+ *
20224
+ * An output exporting under its OWN name is not a collision: the alias rewrites
20225
+ * the identical key with the identical value, and both bags then carry the same
20226
+ * source.
20227
+ *
20228
+ * Deliberately NOT extended to two outputs sharing one `Export.Name` with no
20229
+ * output of that name. Both bags stay consistent there (one iteration writes
20230
+ * both the value and its source), so it is not this issue's class — see
20231
+ * `docs/cross-stack-references.md`.
20232
+ */
20233
+ function isExportAliasCollision(exportName, outputKey, ownedOutputNames) {
20234
+ return exportName !== outputKey && ownedOutputNames.has(exportName);
20235
+ }
20236
+ /**
20237
+ * Mirrors `secret-redaction`'s own `MIN_NEEDLE_LENGTH`. Duplicated rather than
20238
+ * imported because the two bounds answer different questions and should be free
20239
+ * to diverge: that one bounds what may be REWRITTEN, this one what may be
20240
+ * REFUSED or REPORTED.
20241
+ */
20242
+ const MIN_SECRET_NEEDLE = 4;
20243
+ /**
20244
+ * Which recorded secrets are visible in `text`, or `undefined` when none is.
20245
+ *
20246
+ * ONE rule for both callers below, because they were inconsistent and the
20247
+ * inconsistency was a hole: the export-name check refused only an exact
20248
+ * whole-name match while the state-KEY scan did bounded containment over the
20249
+ * same kind of map — so `prod-<secret>-endpoint` was written as a state key by
20250
+ * the deploy and then reported by `cdkd scrub` as an UNREPAIRABLE leak. The tool
20251
+ * was creating the exact state it tells the user it cannot fix.
20252
+ *
20253
+ * A WHOLE-value match counts at any length; an EMBEDDED one only at or above
20254
+ * {@link MIN_SECRET_NEEDLE}. The bound is what makes containment safe to use for
20255
+ * a refusal at all — an unbounded scan over a degenerate one-character secret
20256
+ * matches almost every name and silently drops working exports — and its cost
20257
+ * is stated rather than hidden: a secret of three characters or fewer embedded
20258
+ * in a longer string is not seen. That value is indistinguishable from
20259
+ * coincidence, and the same bound already governs every substring redaction
20260
+ * cdkd performs.
20261
+ *
20262
+ * No empty-string case is needed at either caller: the resolver never records an
20263
+ * empty secret (`secret-redaction` says so — an empty needle would match every
20264
+ * leaf), and the length bound excludes it from the containment arm regardless.
20265
+ */
20266
+ function secretsPresentIn(text, secrets) {
20267
+ if (!secrets || secrets.size === 0) return void 0;
20268
+ const exposure = /* @__PURE__ */ new Map();
20269
+ for (const [plaintext, expression] of secrets) if (text === plaintext || plaintext.length >= MIN_SECRET_NEEDLE && text.includes(plaintext)) exposure.set(plaintext, expression);
20270
+ return exposure.size > 0 ? exposure : void 0;
20271
+ }
20272
+ /**
20273
+ * The secrets present in a resolved `Export.Name`, or `undefined` when it
20274
+ * carries none.
20275
+ *
20276
+ * An `Export.Name` may be an intrinsic (`Fn::Sub` / `Fn::Join`), and those
20277
+ * substitute dynamic references — so the resolved name can contain a resolved
20278
+ * secret. That name would become a state KEY, and every redaction pass walks
20279
+ * VALUES only, so the plaintext would land in `state.json` and be republished
20280
+ * into the exports index.
20281
+ *
20282
+ * TWO signals, and neither subsumes the other:
20283
+ *
20284
+ * - `substitutedIntoName` — the caller resolves the name with its OWN
20285
+ * `recordedSecretValues` map, so a non-empty map means the resolver
20286
+ * substituted a secret INTO THIS NAME. Exact, and the only arm that can see a
20287
+ * substituted secret SHORTER than the containment bound below.
20288
+ * - a bounded containment scan of `recordedThisPass` ({@link secretsPresentIn}),
20289
+ * which catches plaintext that arrived by any other route — a literal name, a
20290
+ * cache hit, an `Fn::Sub` variable echoing the value.
20291
+ *
20292
+ * An earlier revision had only the first, calling the scan unpromising because
20293
+ * an UNBOUNDED one is: a degenerate one-character recorded secret would make
20294
+ * every name containing that character "secret" and silently drop working
20295
+ * exports. The bound is what makes the scan safe, and without the scan the
20296
+ * deploy wrote `prod-<secret>-endpoint` as a state key that `cdkd scrub` then
20297
+ * reported as unrepairable.
20298
+ *
20299
+ * The containment arm applies with no order caveat any more: the deploy engine
20300
+ * resolves EVERY output value
20301
+ * before deciding any alias, so this arm sees the complete map whatever the
20302
+ * declaration order. (An earlier revision of this paragraph said catching that
20303
+ * required "resolving the whole template before deciding anything" and called
20304
+ * it impractical — the value pass already does exactly that, at no extra cost.
20305
+ * The residual is now only a secret first substituted by ANOTHER output's
20306
+ * `Export.Name`, since those resolve in the second pass, in declaration order.)
20307
+ *
20308
+ * No empty-string special case, in either map: the resolver never records an
20309
+ * empty secret (`secret-redaction.ts` states it — an empty needle would match
20310
+ * every leaf), so an `''` key cannot reach here, and a guard against it would
20311
+ * be a branch no test could ever distinguish.
20312
+ */
20313
+ function exportNameSecretExposure(exportName, substitutedIntoName, recordedThisPass) {
20314
+ const exposure = new Map(substitutedIntoName);
20315
+ for (const [plaintext, expression] of secretsPresentIn(exportName, recordedThisPass) ?? []) exposure.set(plaintext, expression);
20316
+ return exposure.size > 0 ? exposure : void 0;
20317
+ }
20318
+ /**
20319
+ * Secrets visible in a state KEY, or `undefined` when there are none.
20320
+ *
20321
+ * Unlike {@link exportNameSecretExposure} there is no resolution to attribute
20322
+ * this to: the key was written by an EARLIER binary, so containment is the only
20323
+ * available signal. It is therefore bounded the same way `secret-redaction`
20324
+ * bounds its own substring scan — a value shorter than {@link MIN_SECRET_NEEDLE}
20325
+ * is matched only as the WHOLE key — because an unbounded containment scan over
20326
+ * a degenerate short secret flags every key in the state and fails the
20327
+ * `--dry-run --fail` CI gate repo-wide, which is the availability failure the
20328
+ * export-name check was redesigned to avoid.
20329
+ *
20330
+ * Bound and cost are {@link secretsPresentIn}'s; see there.
20331
+ */
20332
+ function stateKeySecretExposure(key, secrets) {
20333
+ return secretsPresentIn(key, secrets);
20334
+ }
20335
+ /**
20336
+ * Replace EVERY occurrence of every exposed secret with the mask.
20337
+ *
20338
+ * Deliberately not `maskSecretsInText`: that helper skips needles shorter than
20339
+ * its minimum length, which is right when scanning arbitrary bags for
20340
+ * coincidental matches but wrong here, where the values are known to be IN this
20341
+ * string. Feeding a detected-but-unmaskable name to it printed the secret under
20342
+ * a "masked:" label — a message asserting a protection it had not performed.
20343
+ * Longest-first so a secret containing another is masked whole.
20344
+ */
20345
+ function maskEveryOccurrence(text, exposure) {
20346
+ let out = text;
20347
+ for (const value of Array.from(exposure.keys()).sort((a, b) => b.length - a.length)) out = out.split(value).join("***");
20348
+ return out;
20349
+ }
20350
+ /**
20351
+ * Warning for an `Export.Name` that resolved to something containing secret
20352
+ * plaintext. Refused rather than published: the name would be a state KEY, and
20353
+ * keys are never redacted.
20354
+ *
20355
+ * The name is shown MASKED, and omitted entirely if masking somehow left it
20356
+ * unchanged. stderr is a reader like any other, so the invariant is absolute: a
20357
+ * message must never claim a masking it did not perform.
20358
+ */
20359
+ function secretBearingExportNameWarning(outputKey, exportName, exposure) {
20360
+ const masked = stripControlChars(maskEveryOccurrence(exportName, exposure));
20361
+ const shown = masked === stripControlChars(exportName) ? "" : `(masked: "${masked}") `;
20362
+ 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.`;
20363
+ }
20364
+ /**
20365
+ * Warning for a state KEY that already holds secret plaintext — the residue an
20366
+ * EARLIER binary left when it published an export name that resolved to one.
20367
+ *
20368
+ * `cdkd scrub` cannot repair this. Every redaction pass rewrites VALUES; a key
20369
+ * is the export's identity, so renaming it here would silently retire an export
20370
+ * consumers resolve by name, and dropping it would delete a live export. The
20371
+ * remedy is in the template: give the output a non-secret `Export.Name` and
20372
+ * redeploy, which rewrites `state.outputs` wholesale and republishes the index.
20373
+ * Reported so the `--dry-run --fail` CI gate stops calling such a state clean.
20374
+ */
20375
+ function secretBearingStateKeyWarning(stackName, key, exposure) {
20376
+ return `State for ${stripControlChars(stackName)} holds an output KEY containing a secret (masked: "${stripControlChars(maskEveryOccurrence(key, exposure))}") — cdkd scrub cannot rewrite a key, only a value, because the key IS the export name consumers resolve by. Give that output a non-secret Export.Name and redeploy: the next deploy replaces state.outputs and the exports index entirely. ROTATE the exposed secret.`;
20377
+ }
20378
+ /**
20379
+ * Warning for an `Export.Name` colliding with an output NAME it does not own.
20380
+ *
20381
+ * Names both outputs because the two are equally likely to be the mistake, and
20382
+ * says which value survives — the export is skipped, so the key keeps the
20383
+ * output's own value.
20384
+ *
20385
+ * No masking here, and none is needed: the name printed is one that MATCHED a
20386
+ * declared output name, i.e. template text, and a name carrying a secret is
20387
+ * refused by {@link secretBearingExportNameWarning} before this is reached.
20388
+ */
20389
+ function exportAliasCollisionWarning(outputKey, exportName) {
20390
+ const shown = stripControlChars(exportName);
20391
+ const from = stripControlChars(outputKey);
20392
+ return `Output ${from} exports as "${shown}", which is also the name of another output in this stack — skipping the export alias, so output ${shown} keeps its own value and the export is not published. A consumer's Fn::ImportValue on "${shown}" therefore resolves to output ${shown}, NOT to ${from} (CloudFormation would publish both). Rename the export, or the colliding output.`;
20393
+ }
20394
+ /**
20395
+ * Warning for the same collision seen by `cdkd scrub`, whose remedy differs.
20396
+ *
20397
+ * Scrub does not resolve outputs — it redacts state written by an EARLIER
20398
+ * binary, where the alias may have won the colliding key — so it cannot claim
20399
+ * the key belongs to either output. It drops the position source for that key
20400
+ * and lets the value scan decide from the plaintext actually stored, which is
20401
+ * why this message promises something weaker than the deploy-time one. It can
20402
+ * also fire on a template the deploy handled cleanly, per
20403
+ * {@link collectDeclaredOutputNames}.
20404
+ */
20405
+ function exportAliasCollisionScrubWarning(outputKey, exportName, secrets) {
20406
+ const exposure = secretsPresentIn(exportName, secrets);
20407
+ const shown = stripControlChars(exposure ? maskEveryOccurrence(exportName, exposure) : exportName);
20408
+ return `Output ${stripControlChars(outputKey)} exports as "${shown}", which is also the name of another output in this stack — state cannot say which of the two the stored value under "${shown}" came from, so that key is redacted by value match instead of by template position, and two references resolving to the same value could still collapse there. Rename the export, or the colliding output, and redeploy.`;
20409
+ }
20410
+
20134
20411
  //#endregion
20135
20412
  //#region src/utils/colors.ts
20136
20413
  /**
@@ -22294,7 +22571,7 @@ const FLUSH_INTERVAL_MS = 2e3;
22294
22571
  const FLUSH_EVENT_THRESHOLD = 50;
22295
22572
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
22296
22573
  function getCdkdVersion() {
22297
- return "0.283.31";
22574
+ return "0.283.32";
22298
22575
  }
22299
22576
  /**
22300
22577
  * Generate a time-sortable unique run id, e.g.
@@ -22935,6 +23212,14 @@ var DeployEngine = class {
22935
23212
  */
22936
23213
  outputsTemplateSource = {};
22937
23214
  /**
23215
+ * Whether {@link outputsTemplateSource} may be used to POSITION the outputs
23216
+ * redaction. False once an outputs pass threw partway: the post-loop
23217
+ * name pass never ran, so the bag holds only the alias keys written before
23218
+ * the throw — a partial source built from THIS template, while the bag the
23219
+ * failure path then redacts is the PREVIOUS deploy's. Reset per `deploy()`.
23220
+ */
23221
+ outputsSourceUsable = true;
23222
+ /**
22938
23223
  * Per-logical-id snapshot of the intrinsic-RESOLVED desired properties
22939
23224
  * each CREATE / UPDATE attempted (issue #1198). Written just before the
22940
23225
  * provider call; read only when the op FAILS, to journal the failed op's
@@ -22979,6 +23264,7 @@ var DeployEngine = class {
22979
23264
  this.perResourceTemplateProps = /* @__PURE__ */ new Map();
22980
23265
  this.outputSecrets = /* @__PURE__ */ new Map();
22981
23266
  this.outputsTemplateSource = {};
23267
+ this.outputsSourceUsable = true;
22982
23268
  this.resolver.resetPhysicalIdFallbackCount();
22983
23269
  return withStackName(stackName, () => this.doDeploy(stackName, template));
22984
23270
  }
@@ -23022,7 +23308,7 @@ var DeployEngine = class {
23022
23308
  */
23023
23309
  redactOutputs(outputs) {
23024
23310
  if (this.outputSecrets.size === 0) return outputs;
23025
- return redactSecretsForState(outputs, this.outputSecrets, this.outputsTemplateSource);
23311
+ return redactSecretsForState(outputs, this.outputSecrets, this.outputsSourceUsable ? this.outputsTemplateSource : void 0);
23026
23312
  }
23027
23313
  /**
23028
23314
  * Redact resolved secret plaintext out of rollback-journal operations (GHSA
@@ -24626,9 +24912,30 @@ var DeployEngine = class {
24626
24912
  });
24627
24913
  }
24628
24914
  /**
24629
- * Resolve stack outputs from template and resource attributes
24915
+ * What a failed Output resolution does, shared by both passes of
24916
+ * {@link resolveOutputs} so they cannot drift — the alias pass reports the
24917
+ * SAME failure for a name it could not resolve as the value pass does for a
24918
+ * value, which is what the single-pass shape did when both lived in one
24919
+ * `try`.
24630
24920
  *
24631
- * Uses IntrinsicFunctionResolver for full CloudFormation intrinsic function support.
24921
+ * Issue #1111 item 2: under `--strict-getatt` an unresolvable Output fails
24922
+ * the deploy instead of silently publishing nothing (which breaks downstream
24923
+ * `Fn::ImportValue` consumers with "export not found" long after this deploy
24924
+ * exits 0).
24925
+ */
24926
+ handleOutputResolutionFailure(error, outputKey, outputs) {
24927
+ if (this.options.strictGetAtt) throw new Error(`Failed to resolve output ${outputKey}: ${error instanceof Error ? error.message : String(error)} (--strict-getatt promotes output resolution failures to deploy errors; drop the flag to warn and skip the output instead)`, { cause: error });
24928
+ this.logger.warn(`Failed to resolve output ${outputKey}: ${String(error)}`);
24929
+ outputs[outputKey] = void 0;
24930
+ }
24931
+ /**
24932
+ * Resolve stack outputs from template and resource attributes.
24933
+ *
24934
+ * Uses `IntrinsicFunctionResolver` for full CloudFormation intrinsic function
24935
+ * support, and runs in TWO passes — every value, then every export alias —
24936
+ * so an alias decision sees the complete set of secrets this pass resolved
24937
+ * rather than whatever the declaration order happened to have recorded by
24938
+ * then (issue #1919).
24632
24939
  */
24633
24940
  async resolveOutputs(template, resources, stackName, parameterValues, conditions) {
24634
24941
  if (!template.Outputs) return {};
@@ -24639,29 +24946,58 @@ var DeployEngine = class {
24639
24946
  ...parameterValues && { parameters: parameterValues },
24640
24947
  ...conditions && { conditions }
24641
24948
  }, stackName);
24642
- for (const [outputKey, output] of Object.entries(template.Outputs)) {
24643
- if (output.Condition !== void 0 && conditions?.[output.Condition] === false) {
24644
- this.logger.debug(`Skipping output ${outputKey} — condition ${output.Condition} is false`);
24645
- continue;
24949
+ const publishedOutputNames = collectPublishedOutputNames(template.Outputs, conditions);
24950
+ let outputsPassCompleted = false;
24951
+ try {
24952
+ for (const [outputKey, output] of Object.entries(template.Outputs)) {
24953
+ if (isOutputSuppressedByCondition(output, conditions)) {
24954
+ this.logger.debug(`Skipping output ${outputKey} — condition ${output.Condition} is false`);
24955
+ continue;
24956
+ }
24957
+ try {
24958
+ outputs[outputKey] = await this.resolver.resolve(output.Value, context);
24959
+ } catch (error) {
24960
+ this.handleOutputResolutionFailure(error, outputKey, outputs);
24961
+ }
24646
24962
  }
24647
- try {
24648
- const value = await this.resolver.resolve(output.Value, context);
24649
- outputs[outputKey] = value;
24650
- if (output.Export?.Name) {
24651
- const exportName = typeof output.Export.Name === "string" ? output.Export.Name : await this.resolver.resolve(output.Export.Name, context);
24652
- if (typeof exportName === "string") {
24653
- outputs[exportName] = value;
24654
- this.outputsTemplateSource[exportName] = output.Value;
24963
+ for (const [outputKey, output] of Object.entries(template.Outputs)) {
24964
+ if (isOutputSuppressedByCondition(output, conditions)) continue;
24965
+ if (!output.Export?.Name) continue;
24966
+ const value = outputs[outputKey];
24967
+ if (value === void 0) continue;
24968
+ const nameSecrets = /* @__PURE__ */ new Map();
24969
+ let exportName;
24970
+ try {
24971
+ try {
24972
+ exportName = typeof output.Export.Name === "string" ? output.Export.Name : await this.resolver.resolve(output.Export.Name, {
24973
+ ...context,
24974
+ recordedSecretValues: nameSecrets
24975
+ });
24976
+ } finally {
24977
+ for (const [plaintext, expression] of nameSecrets) context.recordedSecretValues?.set(plaintext, expression);
24655
24978
  }
24979
+ } catch (error) {
24980
+ this.handleOutputResolutionFailure(error, outputKey, outputs);
24981
+ continue;
24982
+ }
24983
+ if (typeof exportName !== "string") continue;
24984
+ const exposure = exportNameSecretExposure(exportName, nameSecrets, context.recordedSecretValues);
24985
+ if (exposure) this.logger.warn(secretBearingExportNameWarning(outputKey, exportName, exposure));
24986
+ else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) this.logger.warn(exportAliasCollisionWarning(outputKey, exportName));
24987
+ else {
24988
+ outputs[exportName] = value;
24989
+ this.outputsTemplateSource[exportName] = output.Value;
24656
24990
  }
24657
- } catch (error) {
24658
- if (this.options.strictGetAtt) throw new Error(`Failed to resolve output ${outputKey}: ${error instanceof Error ? error.message : String(error)} (--strict-getatt promotes output resolution failures to deploy errors; drop the flag to warn and skip the output instead)`, { cause: error });
24659
- this.logger.warn(`Failed to resolve output ${outputKey}: ${String(error)}`);
24660
- outputs[outputKey] = void 0;
24661
24991
  }
24992
+ outputsPassCompleted = true;
24993
+ } finally {
24994
+ if (context.recordedSecretValues) for (const [value, expr] of context.recordedSecretValues) this.outputSecrets.set(value, expr);
24995
+ if (!outputsPassCompleted) this.outputsSourceUsable = false;
24996
+ }
24997
+ for (const [outputKey, output] of Object.entries(template.Outputs)) {
24998
+ if (!publishedOutputNames.has(outputKey)) continue;
24999
+ this.outputsTemplateSource[outputKey] = output.Value;
24662
25000
  }
24663
- if (context.recordedSecretValues) for (const [value, expr] of context.recordedSecretValues) this.outputSecrets.set(value, expr);
24664
- for (const [outputKey, output] of Object.entries(template.Outputs)) this.outputsTemplateSource[outputKey] = output.Value;
24665
25001
  return outputs;
24666
25002
  }
24667
25003
  buildDisplayOutputs(template, resolvedOutputs) {
@@ -24676,5 +25012,5 @@ var DeployEngine = class {
24676
25012
  };
24677
25013
 
24678
25014
  //#endregion
24679
- export { coerceCfnBoolean as $, __exportAll as $n, getLegacyStateBucketName as $t, cyan as A, DeployCancelledError as An, buildAssetRedirectMap as At, findSilentDropProperties as B, ResourceUpdateNotSupportedError as Bn, validateAssetBucketName as Bt, makeCanonicalizePropertiesFn as C, getAwsClients as Cn, LockManager as Ct, renderStatefulReason as D, CdkdError as Dn, AssetPublisher as Dt, isStatefulRecreateTargetSync as E, AssetError as En, shouldRetainResource as Et, IAMRoleProvider as F, MissingCdkCliError as Fn, AssetModeResolver as Ft, IntrinsicFunctionResolver as G, formatError as Gn, getDockerCmd as Gt, slowCcOperationTimeoutMs as H, StackTerminationProtectionError as Hn, buildDenyExternalAccessPolicy as Ht, collectInlinePolicyNamesManagedBySiblings as I, NestedStackChildDirectDestroyError as In, BOOTSTRAP_MARKER_PREFIX as It, refStateLookupFromResource as J, withErrorHandling as Jn, AssetManifestLoader as Jt, cfnRefValueFromPhysicalId as K, isCdkdError as Kn, runDockerForeground as Kt, clearOnUpdateRemoval as L, PartialFailureError as Ln, ensureAssetStorage as Lt, green as M, LocalMigrateError as Mn, loadPublishableAssetManifest as Mt, red as N, LocalStartServiceError as Nn, rewriteTemplateAssetReferences as Nt, formatResourceLine as O, ConfigError as On, stringifyValue as Ot, yellow as P, LockError as Pn, escapeRegExp$1 as Pt, assertRegionMatch as Q, markNonRetryable as Qn, getDefaultStateBucketName as Qt, ProviderRegistry as R, ProvisioningError as Rn, getBootstrapMarkerKey as Rt, unsupportedFinalSnapshotError as S, AwsClients as Sn, TemplateParser as St, MULTI_REGION_RECREATE_BLOCKED_TYPES as T, setAwsClients as Tn, rebuildClientForBucketRegion as Tt, disableInstanceApiTermination as U, StateError as Un, buildDockerImage as Ut, CloudControlProvider as V, StackHasActiveImportsError as Vn, validateContainerRepoName as Vt, isTerminationProtectionPropagationError as W, SynthesisError as Wn, formatDockerLoginError as Wt, normalizeAwsTagsToCfn as X, isRetryableTransientError as Xn, Synthesizer as Xt, WAFv2WebACLProvider as Y, isMarkedNonRetryable as Yn, getDockerImageBySourceHash as Yt, resolveExplicitPhysicalId as Z, isThrottlingError as Zn, synthesisStatusMessage as Zt, buildFinalSnapshotIdentifier as _, derivePartitionAndUrlSuffix as _n, DiffCalculator as _t, DeploymentEventsStore as a, resolveStateBucketWithDefaultAndSource as an, requireConfigObject as at, isFinalSnapshotError as b, clearBucketRegionCache as bn, withRetry as bt, replayFailedOperations as c, warnDeprecatedNoPrefixCliFlag as cn, TEMPLATE_SOURCED_RULES as ct, deleteSkipReason as d, MIGRATE_TMP_PREFIX as dn, s3BucketArn as dt, resolveApp as en, configBooleanRefusal as et, withResourceDeadline as f, findLargeInlineResources as fn, s3BucketDomainName as ft, PRE_DELETE_SNAPSHOT_TYPES as g, canonicalizeRegion as gn, applyRoleArnIfSet as gt, ATOMIC_FINAL_SNAPSHOT_TYPES as h, PARTITION_TABLE as hn, s3BucketWebsiteUrl as ht, DeploymentEventsReader as i, resolveStateBucketWithDefault as in, requireConfigArray as it, gray as j, LocalInvokeBuildError as jn, createAssetRedirectResolver as jt, bold as k, DependencyError as kn, WorkGraph as kt, replayRollback as l, CFN_TEMPLATE_BODY_LIMIT as ln, redactSecretsForState as lt, computeImplicitDeleteEdges as m, expectedOwnerParam as mn, s3BucketRegionalDomainName as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, resolveCaptureObservedState as nn, readConfigString as nt, planFailedOps as o, resolveUseCdkBootstrapAssets as on, requireConfigString as ot, IMPLICIT_DELETE_DEPENDENCIES as p, uploadCfnTemplate as pn, s3BucketDualStackDomainName as pt, getAccountInfo as q, normalizeAwsError as qn, runDockerStreaming as qt, DeployEngine as r, resolveSkipPrefix as rn, replayWarn as rt, planRollback as s, stateBucketExistenceConfirmed as sn, STATE_SOURCED_CROSS_GENERATION_RULES as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, resolveAutoAssetStorage as tn, configStringRefusal as tt, UNSPECIFIED_SKIP_REASON as u, CFN_TEMPLATE_URL_LIMIT as un, scrubResourceRecord as ut, ccRoutedFinalSnapshotError as v, AssemblyReader as vn, INTRINSIC_KEYS as vt, extractDeploymentEventError as w, resetAwsClients as wn, S3StateBackend as wt, refusesFinalSnapshot as x, resolveBucketRegion as xn, DagBuilder as xt, createPreDeleteFinalSnapshot as y, processStackMessages as yn, describeTypeWithThrottleRetry as yt, findActionableSilentDrops as z, ResourceTimeoutError as zn, parseBootstrapMarker as zt };
24680
- //# sourceMappingURL=deploy-engine-AWFiXyhp.js.map
25015
+ export { getAccountInfo as $, isCdkdError as $n, runDockerForeground as $t, cyan as A, getAwsClients as An, S3StateBackend as At, stateKeySecretExposure as B, LocalStartServiceError as Bn, escapeRegExp$1 as Bt, makeCanonicalizePropertiesFn as C, canonicalizeRegion as Cn, DiffCalculator as Ct, renderStatefulReason as D, clearBucketRegionCache as Dn, DagBuilder as Dt, isStatefulRecreateTargetSync as E, processStackMessages as En, withRetry as Et, collectDeclaredOutputNames as F, ConfigError as Fn, WorkGraph as Ft, findActionableSilentDrops as G, ProvisioningError as Gn, getBootstrapMarkerKey as Gt, collectInlinePolicyNamesManagedBySiblings as H, MissingCdkCliError as Hn, AssetModeResolver as Ht, collectPublishedOutputNames as I, DependencyError as In, buildAssetRedirectMap as It, slowCcOperationTimeoutMs as J, StackHasActiveImportsError as Jn, validateContainerRepoName as Jt, findSilentDropProperties as K, ResourceTimeoutError as Kn, parseBootstrapMarker as Kt, exportAliasCollisionScrubWarning as L, DeployCancelledError as Ln, createAssetRedirectResolver as Lt, green as M, setAwsClients as Mn, shouldRetainResource as Mt, red as N, AssetError as Nn, AssetPublisher as Nt, formatResourceLine as O, resolveBucketRegion as On, TemplateParser as Ot, yellow as P, CdkdError as Pn, stringifyValue as Pt, cfnRefValueFromPhysicalId as Q, formatError as Qn, getDockerCmd as Qt, isExportAliasCollision as R, LocalInvokeBuildError as Rn, loadPublishableAssetManifest as Rt, unsupportedFinalSnapshotError as S, PARTITION_TABLE as Sn, applyRoleArnIfSet as St, MULTI_REGION_RECREATE_BLOCKED_TYPES as T, AssemblyReader as Tn, describeTypeWithThrottleRetry as Tt, clearOnUpdateRemoval as U, NestedStackChildDirectDestroyError as Un, BOOTSTRAP_MARKER_PREFIX as Ut, IAMRoleProvider as V, LockError as Vn, stripControlChars as Vt, ProviderRegistry as W, PartialFailureError as Wn, ensureAssetStorage as Wt, isTerminationProtectionPropagationError as X, StateError as Xn, buildDockerImage as Xt, disableInstanceApiTermination as Y, StackTerminationProtectionError as Yn, buildDenyExternalAccessPolicy as Yt, IntrinsicFunctionResolver as Z, SynthesisError as Zn, formatDockerLoginError as Zt, buildFinalSnapshotIdentifier as _, CFN_TEMPLATE_URL_LIMIT as _n, s3BucketArn as _t, DeploymentEventsStore as a, getDefaultStateBucketName as an, markNonRetryable as ar, coerceCfnBoolean as at, isFinalSnapshotError as b, uploadCfnTemplate as bn, s3BucketRegionalDomainName as bt, replayFailedOperations as c, resolveAutoAssetStorage as cn, readConfigString as ct, deleteSkipReason as d, resolveStateBucketWithDefault as dn, requireConfigObject as dt, runDockerStreaming as en, normalizeAwsError as er, refStateLookupFromResource as et, withResourceDeadline as f, resolveStateBucketWithDefaultAndSource as fn, requireConfigString as ft, PRE_DELETE_SNAPSHOT_TYPES as g, CFN_TEMPLATE_BODY_LIMIT as gn, scrubResourceRecord as gt, ATOMIC_FINAL_SNAPSHOT_TYPES as h, warnDeprecatedNoPrefixCliFlag as hn, redactSecretsForState as ht, DeploymentEventsReader as i, synthesisStatusMessage as in, isThrottlingError as ir, assertRegionMatch as it, gray as j, resetAwsClients as jn, rebuildClientForBucketRegion as jt, bold as k, AwsClients as kn, LockManager as kt, replayRollback as l, resolveCaptureObservedState as ln, replayWarn as lt, computeImplicitDeleteEdges as m, stateBucketExistenceConfirmed as mn, TEMPLATE_SOURCED_RULES as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, getDockerImageBySourceHash as nn, isMarkedNonRetryable as nr, normalizeAwsTagsToCfn as nt, planFailedOps as o, getLegacyStateBucketName as on, __exportAll as or, configBooleanRefusal as ot, IMPLICIT_DELETE_DEPENDENCIES as p, resolveUseCdkBootstrapAssets as pn, STATE_SOURCED_CROSS_GENERATION_RULES as pt, CloudControlProvider as q, ResourceUpdateNotSupportedError as qn, validateAssetBucketName as qt, DeployEngine as r, Synthesizer as rn, isRetryableTransientError as rr, resolveExplicitPhysicalId as rt, planRollback as s, resolveApp as sn, configStringRefusal as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, AssetManifestLoader as tn, withErrorHandling as tr, WAFv2WebACLProvider as tt, UNSPECIFIED_SKIP_REASON as u, resolveSkipPrefix as un, requireConfigArray as ut, ccRoutedFinalSnapshotError as v, MIGRATE_TMP_PREFIX as vn, s3BucketDomainName as vt, extractDeploymentEventError as w, derivePartitionAndUrlSuffix as wn, INTRINSIC_KEYS as wt, refusesFinalSnapshot as x, expectedOwnerParam as xn, s3BucketWebsiteUrl as xt, createPreDeleteFinalSnapshot as y, findLargeInlineResources as yn, s3BucketDualStackDomainName as yt, secretBearingStateKeyWarning as z, LocalMigrateError as zn, rewriteTemplateAssetReferences as zt };
25016
+ //# sourceMappingURL=deploy-engine-rYTNzst0.js.map