|
| 1 | +/** |
| 2 | + * Reduce template to a normal form where asset references have been normalized |
| 3 | + * |
| 4 | + * This makes it possible to compare templates if all that's different between |
| 5 | + * them is the hashes of the asset values. |
| 6 | + * |
| 7 | + * Currently only handles parameterized assets, but can (and should) |
| 8 | + * be adapted to handle convention-mode assets as well when we start using |
| 9 | + * more of those. |
| 10 | + */ |
| 11 | +export function canonicalizeTemplate(template: any): any { |
| 12 | + // For the weird case where we have an array of templates... |
| 13 | + if (Array.isArray(template)) { |
| 14 | + return template.map(canonicalizeTemplate); |
| 15 | + } |
| 16 | + |
| 17 | + // Find assets via parameters |
| 18 | + const stringSubstitutions = new Array<[RegExp, string]>(); |
| 19 | + const paramRe = /^AssetParameters([a-zA-Z0-9]{64})(S3Bucket|S3VersionKey|ArtifactHash)([a-zA-Z0-9]{8})$/; |
| 20 | + |
| 21 | + const assetsSeen = new Set<string>(); |
| 22 | + for (const paramName of Object.keys(template?.Parameters || {})) { |
| 23 | + const m = paramRe.exec(paramName); |
| 24 | + if (!m) { continue; } |
| 25 | + if (assetsSeen.has(m[1])) { continue; } |
| 26 | + |
| 27 | + assetsSeen.add(m[1]); |
| 28 | + const ix = assetsSeen.size; |
| 29 | + |
| 30 | + // Full parameter reference |
| 31 | + stringSubstitutions.push([ |
| 32 | + new RegExp(`AssetParameters${m[1]}(S3Bucket|S3VersionKey|ArtifactHash)([a-zA-Z0-9]{8})`), |
| 33 | + `Asset${ix}$1`, |
| 34 | + ]); |
| 35 | + // Substring asset hash reference |
| 36 | + stringSubstitutions.push([ |
| 37 | + new RegExp(`${m[1]}`), |
| 38 | + `Asset${ix}Hash`, |
| 39 | + ]); |
| 40 | + } |
| 41 | + |
| 42 | + // Substitute them out |
| 43 | + return substitute(template); |
| 44 | + |
| 45 | + function substitute(what: any): any { |
| 46 | + if (Array.isArray(what)) { |
| 47 | + return what.map(substitute); |
| 48 | + } |
| 49 | + |
| 50 | + if (typeof what === 'object' && what !== null) { |
| 51 | + const ret: any = {}; |
| 52 | + for (const [k, v] of Object.entries(what)) { |
| 53 | + ret[stringSub(k)] = substitute(v); |
| 54 | + } |
| 55 | + return ret; |
| 56 | + } |
| 57 | + |
| 58 | + if (typeof what === 'string') { |
| 59 | + return stringSub(what); |
| 60 | + } |
| 61 | + |
| 62 | + return what; |
| 63 | + } |
| 64 | + |
| 65 | + function stringSub(x: string) { |
| 66 | + for (const [re, replacement] of stringSubstitutions) { |
| 67 | + x = x.replace(re, replacement); |
| 68 | + } |
| 69 | + return x; |
| 70 | + } |
| 71 | +} |
0 commit comments