@go-to-k/cdkd 0.28.2 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
package/dist/index.js CHANGED
@@ -4984,6 +4984,9 @@ var IntrinsicFunctionResolver = class {
4984
4984
  if ("Fn::ImportValue" in obj) {
4985
4985
  return await this.resolveImportValue(obj["Fn::ImportValue"], context);
4986
4986
  }
4987
+ if ("Fn::GetStackOutput" in obj) {
4988
+ return await this.resolveGetStackOutput(obj["Fn::GetStackOutput"], context);
4989
+ }
4987
4990
  if ("Fn::FindInMap" in obj) {
4988
4991
  return await this.resolveFindInMap(
4989
4992
  obj["Fn::FindInMap"],
@@ -5532,6 +5535,106 @@ var IntrinsicFunctionResolver = class {
5532
5535
  `Fn::ImportValue: export '${exportName}' not found in any stack. Searched ${allStacks.length} state record(s). Make sure the exporting stack has been deployed and the Output has an Export.Name property.`
5533
5536
  );
5534
5537
  }
5538
+ /**
5539
+ * Resolve Fn::GetStackOutput (cross-stack / cross-region output reference)
5540
+ *
5541
+ * Shape: { "Fn::GetStackOutput": { "StackName": "...", "OutputName": "...",
5542
+ * "Region": "...", "RoleArn": "..." } }
5543
+ *
5544
+ * Unlike Fn::ImportValue, the producer stack is named explicitly and no
5545
+ * Export is required. cdkd reads the producer's `outputs` from the
5546
+ * region-scoped state record at
5547
+ * `s3://{bucket}/cdkd/{StackName}/{Region}/state.json`. When `Region` is
5548
+ * omitted, the consumer's deploy region is used.
5549
+ *
5550
+ * RoleArn (cross-account) is intentionally rejected — cdkd uses S3 state,
5551
+ * not CloudFormation DescribeStacks, so a cross-account reference would
5552
+ * require assuming the role and reading the producer's separate state
5553
+ * bucket. That path is not yet implemented; we surface a clear error
5554
+ * instead of silently downgrading.
5555
+ */
5556
+ async resolveGetStackOutput(arg, context) {
5557
+ if (!arg || typeof arg !== "object" || Array.isArray(arg)) {
5558
+ throw new Error(
5559
+ `Fn::GetStackOutput: argument must be an object with StackName/OutputName/Region/RoleArn, got ${arg === null ? "null" : Array.isArray(arg) ? "array" : typeof arg}`
5560
+ );
5561
+ }
5562
+ const args = arg;
5563
+ if (!("StackName" in args)) {
5564
+ throw new Error("Fn::GetStackOutput: StackName is required");
5565
+ }
5566
+ if (!("OutputName" in args)) {
5567
+ throw new Error("Fn::GetStackOutput: OutputName is required");
5568
+ }
5569
+ const stackName = await this.resolveValue(args["StackName"], context);
5570
+ if (typeof stackName !== "string" || stackName === "") {
5571
+ throw new Error(
5572
+ `Fn::GetStackOutput: StackName must resolve to a non-empty string, got ${typeof stackName}`
5573
+ );
5574
+ }
5575
+ const outputName = await this.resolveValue(args["OutputName"], context);
5576
+ if (typeof outputName !== "string" || outputName === "") {
5577
+ throw new Error(
5578
+ `Fn::GetStackOutput: OutputName must resolve to a non-empty string, got ${typeof outputName}`
5579
+ );
5580
+ }
5581
+ let region = this.resolverRegion;
5582
+ if ("Region" in args && args["Region"] !== void 0 && args["Region"] !== null) {
5583
+ const resolvedRegion = await this.resolveValue(args["Region"], context);
5584
+ if (typeof resolvedRegion !== "string" || resolvedRegion === "") {
5585
+ throw new Error(
5586
+ `Fn::GetStackOutput: Region must resolve to a non-empty string, got ${typeof resolvedRegion}`
5587
+ );
5588
+ }
5589
+ region = resolvedRegion;
5590
+ }
5591
+ let roleArn;
5592
+ if ("RoleArn" in args && args["RoleArn"] !== void 0 && args["RoleArn"] !== null) {
5593
+ const resolvedRoleArn = await this.resolveValue(args["RoleArn"], context);
5594
+ if (typeof resolvedRoleArn !== "string" || resolvedRoleArn === "") {
5595
+ throw new Error(
5596
+ `Fn::GetStackOutput: RoleArn must resolve to a non-empty string, got ${typeof resolvedRoleArn}`
5597
+ );
5598
+ }
5599
+ roleArn = resolvedRoleArn;
5600
+ }
5601
+ if (roleArn) {
5602
+ throw new Error(
5603
+ `Fn::GetStackOutput: cross-account references via RoleArn are not yet supported by cdkd (StackName=${stackName}, Region=${region}, RoleArn=${roleArn}). cdkd reads outputs from S3 state instead of CloudFormation DescribeStacks, so cross-account requires assuming the role and reading the producer account's state bucket \u2014 not yet implemented.`
5604
+ );
5605
+ }
5606
+ if (!context.stateBackend) {
5607
+ throw new Error("Fn::GetStackOutput: state backend is required for cross-stack references");
5608
+ }
5609
+ if (context.stackName && context.stackName === stackName && region === this.resolverRegion) {
5610
+ throw new Error(
5611
+ `Fn::GetStackOutput: cannot reference own stack '${stackName}' in the same region '${region}'`
5612
+ );
5613
+ }
5614
+ this.logger.debug(
5615
+ `Resolving Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName}`
5616
+ );
5617
+ const stateData = await context.stateBackend.getState(stackName, region);
5618
+ if (!stateData) {
5619
+ throw new Error(
5620
+ `Fn::GetStackOutput: stack '${stackName}' not found in region '${region}'. Make sure the producer stack has been deployed via cdkd.`
5621
+ );
5622
+ }
5623
+ const outputs = stateData.state.outputs ?? {};
5624
+ if (!(outputName in outputs)) {
5625
+ const available = Object.keys(outputs).join(", ") || "(none)";
5626
+ throw new Error(
5627
+ `Fn::GetStackOutput: output '${outputName}' not found in stack '${stackName}' (${region}). Available outputs: ${available}`
5628
+ );
5629
+ }
5630
+ const value = outputs[outputName];
5631
+ this.logger.info(
5632
+ `Resolved Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName} -> ${JSON.stringify(
5633
+ value
5634
+ )}`
5635
+ );
5636
+ return value;
5637
+ }
5535
5638
  /**
5536
5639
  * Resolve Fn::FindInMap intrinsic function
5537
5640
  *