export function computeResourceDigests()

in packages/@aws-cdk/toolkit-lib/lib/api/refactoring/digest.ts [28:118]


export function computeResourceDigests(template: CloudFormationTemplate): Record<string, string> {
  const resources = template.Resources || {};
  const graph: Record<string, Set<string>> = {};
  const reverseGraph: Record<string, Set<string>> = {};

  // 1. Build adjacency lists
  for (const id of Object.keys(resources)) {
    graph[id] = new Set();
    reverseGraph[id] = new Set();
  }

  // 2. Detect dependencies by searching for Ref/Fn::GetAtt
  const findDependencies = (value: any): string[] => {
    if (!value || typeof value !== 'object') return [];
    if (Array.isArray(value)) {
      return value.flatMap(findDependencies);
    }
    if ('Ref' in value) {
      return [value.Ref];
    }
    if ('Fn::GetAtt' in value) {
      const refTarget = Array.isArray(value['Fn::GetAtt']) ? value['Fn::GetAtt'][0] : value['Fn::GetAtt'].split('.')[0];
      return [refTarget];
    }
    if ('DependsOn' in value) {
      return [value.DependsOn];
    }
    return Object.values(value).flatMap(findDependencies);
  };

  for (const [id, res] of Object.entries(resources)) {
    const deps = findDependencies(res || {});
    for (const dep of deps) {
      if (dep in resources && dep !== id) {
        graph[id].add(dep);
        reverseGraph[dep].add(id);
      }
    }
  }

  // 3. Topological sort
  const outDegree = Object.keys(graph).reduce((acc, k) => {
    acc[k] = graph[k].size;
    return acc;
  }, {} as Record<string, number>);

  const queue = Object.keys(outDegree).filter((k) => outDegree[k] === 0);
  const order: string[] = [];

  while (queue.length > 0) {
    const node = queue.shift()!;
    order.push(node);
    for (const nxt of reverseGraph[node]) {
      outDegree[nxt]--;
      if (outDegree[nxt] === 0) {
        queue.push(nxt);
      }
    }
  }

  // 4. Compute digests in sorted order
  const result: Record<string, string> = {};
  for (const id of order) {
    const resource = resources[id];
    const resourceProperties = resource.Properties ?? {};
    const model = loadResourceModel(resource.Type);
    const identifier = intersection(Object.keys(resourceProperties), model?.primaryIdentifier ?? []);
    let toHash: string;

    if (identifier.length === model?.primaryIdentifier?.length) {
      // The resource has a physical ID defined, so we can
      // use the ID and the type as the identity of the resource.
      toHash =
        resource.Type +
        identifier
          .sort()
          .map((attr) => JSON.stringify(resourceProperties[attr]))
          .join('');
    } else {
      // The resource does not have a physical ID defined, so we need to
      // compute the digest based on its properties and dependencies.
      const depDigests = Array.from(graph[id]).map((d) => result[d]);
      const propertiesHash = hashObject(stripReferences(stripConstructPath(resource)));
      toHash = resource.Type + propertiesHash + depDigests.join('');
    }

    result[id] = crypto.createHash('sha256').update(toHash).digest('hex');
  }

  return result;
}