@suss/contract-terraform 0.31.2 → 0.33.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.
package/dist/index.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  filterCalls: () => filterCalls,
34
34
  filterTerms: () => filterTerms,
35
35
  filterValuesFor: () => filterValuesFor,
36
+ jsonAttributeValue: () => jsonAttributeValue,
36
37
  parseFilterQuery: () => parseFilterQuery,
37
38
  terraformFileToSummaries: () => terraformFileToSummaries,
38
39
  terraformToSummaries: () => terraformToSummaries
@@ -40,10 +41,360 @@ __export(index_exports, {
40
41
  module.exports = __toCommonJS(index_exports);
41
42
  var import_node_fs = __toESM(require("fs"), 1);
42
43
  var import_node_path = __toESM(require("path"), 1);
43
- var import_hcl2_parser = __toESM(require("hcl2-parser"), 1);
44
44
  var import_semver = __toESM(require("semver"), 1);
45
45
  var import_behavioral_ir = require("@suss/behavioral-ir");
46
46
 
47
+ // src/hclDocument.ts
48
+ var import_hcl2_parser = __toESM(require("hcl2-parser"), 1);
49
+ var WRAPPER = "value";
50
+ function parseHclDocument(source) {
51
+ let read;
52
+ try {
53
+ const [parsed] = import_hcl2_parser.default.parseToObject(source);
54
+ read = parsed;
55
+ } catch {
56
+ return null;
57
+ }
58
+ return typeof read === "object" && read !== null && !Array.isArray(read) ? read : null;
59
+ }
60
+ function parseHclExpression(expression) {
61
+ return parseHclDocument(`${WRAPPER} = ${expression}`)?.[WRAPPER] ?? null;
62
+ }
63
+
64
+ // src/hclValue.ts
65
+ function arrayOf(value) {
66
+ if (value === void 0 || value === null) {
67
+ return [];
68
+ }
69
+ return Array.isArray(value) ? value : [value];
70
+ }
71
+ function asRecord(value) {
72
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
73
+ }
74
+ function stringOf(value) {
75
+ return typeof value === "string" ? value : null;
76
+ }
77
+ function recordsIn(value) {
78
+ return arrayOf(value).map(asRecord).filter((record) => record !== null);
79
+ }
80
+ function mapStrings(value, leaf) {
81
+ if (typeof value === "string") {
82
+ return leaf(value);
83
+ }
84
+ if (Array.isArray(value)) {
85
+ return value.map((entry) => mapStrings(entry, leaf));
86
+ }
87
+ const record = asRecord(value);
88
+ if (record === null) {
89
+ return value;
90
+ }
91
+ return Object.fromEntries(
92
+ Object.entries(record).map(([name, entry]) => [
93
+ name,
94
+ mapStrings(entry, leaf)
95
+ ])
96
+ );
97
+ }
98
+ function someString(value, test) {
99
+ if (typeof value === "string") {
100
+ return test(value);
101
+ }
102
+ if (Array.isArray(value)) {
103
+ return value.some((entry) => someString(entry, test));
104
+ }
105
+ return Object.values(asRecord(value) ?? {}).some(
106
+ (entry) => someString(entry, test)
107
+ );
108
+ }
109
+
110
+ // src/mapValue.ts
111
+ var WHOLE_INTERPOLATION = /^\$\{([\s\S]*)\}$/;
112
+ var MERGE_CALL = /^merge\(([\s\S]*)\)$/;
113
+ var LOCAL_MAP = /^local\.([A-Za-z_][\w-]*)$/;
114
+ var VARIABLE_MAP = /^var\.([A-Za-z_][\w-]*)$/;
115
+ var CHAIN_LIMIT = 4;
116
+ var OPENERS = "([{";
117
+ var CLOSERS = ")]}";
118
+ function statedMap(value, scope) {
119
+ return mapFrom(value, scope, 0);
120
+ }
121
+ function mapFrom(value, scope, depth) {
122
+ if (depth > CHAIN_LIMIT) {
123
+ return null;
124
+ }
125
+ const record = asRecord(value);
126
+ if (record !== null) {
127
+ return record;
128
+ }
129
+ if (typeof value !== "string") {
130
+ return null;
131
+ }
132
+ const inner = WHOLE_INTERPOLATION.exec(value.trim());
133
+ return inner === null ? null : mapFromExpression(inner[1].trim(), scope, depth);
134
+ }
135
+ function mapFromExpression(expression, scope, depth) {
136
+ const merged = MERGE_CALL.exec(expression);
137
+ if (merged !== null) {
138
+ return mergedMaps(splitArguments(merged[1]), scope, depth);
139
+ }
140
+ const local = LOCAL_MAP.exec(expression);
141
+ if (local !== null) {
142
+ return mapFrom(scope.locals[local[1]], scope, depth + 1);
143
+ }
144
+ const variable = VARIABLE_MAP.exec(expression);
145
+ if (variable !== null) {
146
+ const name = variable[1];
147
+ return mapFrom(scope.arguments[name], scope, depth + 1) ?? mapFrom(scope.defaults[name], scope, depth + 1);
148
+ }
149
+ return null;
150
+ }
151
+ function mergedMaps(argumentTexts, scope, depth) {
152
+ const merged = {};
153
+ for (const text of argumentTexts) {
154
+ const stated = mapFrom(parseHclExpression(text), scope, depth + 1);
155
+ if (stated === null) {
156
+ return null;
157
+ }
158
+ Object.assign(merged, stated);
159
+ }
160
+ return merged;
161
+ }
162
+ function splitArguments(text) {
163
+ const found = [];
164
+ let depth = 0;
165
+ let quoted = false;
166
+ let start = 0;
167
+ for (let at = 0; at < text.length; at += 1) {
168
+ const character = text[at];
169
+ if (quoted) {
170
+ quoted = character !== '"' || text[at - 1] === "\\";
171
+ continue;
172
+ }
173
+ if (character === '"') {
174
+ quoted = true;
175
+ } else if (OPENERS.includes(character)) {
176
+ depth += 1;
177
+ } else if (CLOSERS.includes(character)) {
178
+ depth -= 1;
179
+ } else if (character === "," && depth === 0) {
180
+ found.push(text.slice(start, at));
181
+ start = at + 1;
182
+ }
183
+ }
184
+ found.push(text.slice(start));
185
+ return found.map((argument) => argument.trim()).filter((argument) => argument.length > 0);
186
+ }
187
+
188
+ // src/references.ts
189
+ var SUB_TOKEN = /\$\{([^}]*)\}/g;
190
+ function replaceInterpolations(text, settle) {
191
+ return text.replace(
192
+ SUB_TOKEN,
193
+ (written, inner) => settle(inner.trim()) ?? written
194
+ );
195
+ }
196
+ function interpolatedReferences(text) {
197
+ return [...text.matchAll(SUB_TOKEN)].map(
198
+ (match) => match[1].trim()
199
+ );
200
+ }
201
+ var RESOURCE_ATTRIBUTE = /^([A-Za-z][\w-]*)\.([A-Za-z_][\w-]*)\.([A-Za-z_][\w-]*)$/;
202
+ var WHOLE_REFERENCE = /^\$\{([^}]*)\}$/;
203
+ var LOCAL_VALUE = /^local\.([A-Za-z_][\w-]*)$/;
204
+ var VARIABLE_VALUE = /^var\.([A-Za-z_][\w-]*)$/;
205
+ var CHAIN_LIMIT2 = 4;
206
+ function referenceScope(opts) {
207
+ const resources = /* @__PURE__ */ new Map();
208
+ for (const [resourceType, label, body] of opts.resources) {
209
+ const address = `${resourceType}.${label}`;
210
+ if (!resources.has(address)) {
211
+ resources.set(address, body);
212
+ }
213
+ }
214
+ return {
215
+ resources,
216
+ locals: firstOfEach(opts.locals ?? []),
217
+ arguments: opts.arguments ?? {},
218
+ defaults: opts.defaults ?? {},
219
+ namePrefix: opts.namePrefix ?? ""
220
+ };
221
+ }
222
+ function firstOfEach(blocks) {
223
+ const stated = {};
224
+ for (const block of blocks) {
225
+ for (const [name, value] of Object.entries(block)) {
226
+ if (!(name in stated)) {
227
+ stated[name] = value;
228
+ }
229
+ }
230
+ }
231
+ return stated;
232
+ }
233
+ function referencedResource(value, scope) {
234
+ const whole = WHOLE_REFERENCE.exec(value.trim());
235
+ if (whole === null) {
236
+ return null;
237
+ }
238
+ const parsed = RESOURCE_ATTRIBUTE.exec(whole[1].trim());
239
+ if (parsed === null) {
240
+ return null;
241
+ }
242
+ const [, resourceType, label] = parsed;
243
+ return scope.resources.has(`${resourceType}.${label}`) ? `${scope.namePrefix}${label}` : null;
244
+ }
245
+ function resolveReferences(value, scope) {
246
+ return expand(value, scope, []) ?? value;
247
+ }
248
+ function expand(value, scope, resolving) {
249
+ let cycled = false;
250
+ const expandOne = (reference) => {
251
+ if (resolving.includes(reference)) {
252
+ cycled = true;
253
+ return null;
254
+ }
255
+ const stated = statedValue(reference, scope);
256
+ if (stated === null || resolving.length >= CHAIN_LIMIT2) {
257
+ return null;
258
+ }
259
+ const nested = expand(stated, scope, [...resolving, reference]);
260
+ if (nested === null) {
261
+ cycled = true;
262
+ return null;
263
+ }
264
+ return nested;
265
+ };
266
+ const expanded = replaceInterpolations(value, expandOne);
267
+ return cycled ? null : expanded;
268
+ }
269
+ function statedValue(reference, scope) {
270
+ const local = LOCAL_VALUE.exec(reference);
271
+ if (local !== null) {
272
+ return stringOrNull(scope.locals[local[1]]);
273
+ }
274
+ const variable = VARIABLE_VALUE.exec(reference);
275
+ if (variable !== null) {
276
+ return stringOrNull(scope.arguments[variable[1]]);
277
+ }
278
+ const parsed = RESOURCE_ATTRIBUTE.exec(reference);
279
+ if (parsed === null) {
280
+ return null;
281
+ }
282
+ const [, resourceType, label, attribute] = parsed;
283
+ return stringOrNull(
284
+ scope.resources.get(`${resourceType}.${label}`)?.[attribute]
285
+ );
286
+ }
287
+ function stringOrNull(value) {
288
+ return typeof value === "string" ? value : null;
289
+ }
290
+
291
+ // src/blockExpansion.ts
292
+ var DYNAMIC = "dynamic";
293
+ var BARE_NAME = /^\$\{([A-Za-z_][\w-]*)\}$/;
294
+ var FOR_EXPRESSION = /^\$\{\s*\[\s*for\s+([A-Za-z_]\w*)\s*,\s*([A-Za-z_]\w*)\s+in\s+([^:]+):([\s\S]*)\]\s*\}$/;
295
+ function dynamicBlocks(body, block, scope) {
296
+ const written = [];
297
+ const labelled = arrayOf(body[DYNAMIC]).flatMap(
298
+ (entry) => recordsIn(asRecord(entry)?.[block])
299
+ );
300
+ for (const declared of labelled) {
301
+ const stated = statedMap(declared.for_each, scope);
302
+ if (stated === null) {
303
+ continue;
304
+ }
305
+ const iterator = iteratorName(declared, block);
306
+ for (const [key, value] of Object.entries(stated)) {
307
+ const settle = entrySettle(iterator, key, value);
308
+ for (const content of recordsIn(declared.content)) {
309
+ written.push(...filledBlock(content, settle, [iterator]));
310
+ }
311
+ }
312
+ }
313
+ return written;
314
+ }
315
+ function filledBlock(content, settle, iterators) {
316
+ const filled = substituted(content, settle);
317
+ return mentions(filled, iterators) ? [] : [filled];
318
+ }
319
+ function iteratedRecords(value, scope) {
320
+ if (typeof value !== "string") {
321
+ return null;
322
+ }
323
+ const parsed = FOR_EXPRESSION.exec(value.trim());
324
+ if (parsed === null) {
325
+ return null;
326
+ }
327
+ const [, keyName, valueName, collection, body] = parsed;
328
+ const stated = statedMap(`\${${collection.trim()}}`, scope);
329
+ if (stated === null) {
330
+ return null;
331
+ }
332
+ const template = parseBody(body);
333
+ if (template === null) {
334
+ return null;
335
+ }
336
+ return Object.entries(stated).flatMap(
337
+ ([key, entry]) => filledBlock(
338
+ template,
339
+ pairSettle(keyName, valueName, key, entry),
340
+ [keyName, valueName]
341
+ )
342
+ );
343
+ }
344
+ function parseBody(body) {
345
+ const trimmed = body.trim();
346
+ return trimmed.startsWith("{") ? asRecord(parseHclExpression(trimmed)) : null;
347
+ }
348
+ function iteratorName(declared, block) {
349
+ const stated = declared.iterator;
350
+ if (typeof stated !== "string") {
351
+ return block;
352
+ }
353
+ const bare = BARE_NAME.exec(stated.trim());
354
+ return bare === null ? block : bare[1];
355
+ }
356
+ function entrySettle(iterator, key, value) {
357
+ return (reference) => {
358
+ const steps = reference.split(".");
359
+ if (steps[0] !== iterator) {
360
+ return null;
361
+ }
362
+ if (steps.length === 2 && steps[1] === "key") {
363
+ return key;
364
+ }
365
+ return steps[1] === "value" ? fieldOf(value, steps.slice(2)) : null;
366
+ };
367
+ }
368
+ function pairSettle(keyName, valueName, key, value) {
369
+ return (reference) => {
370
+ const steps = reference.split(".");
371
+ if (steps.length === 1 && steps[0] === keyName) {
372
+ return key;
373
+ }
374
+ return steps[0] === valueName ? fieldOf(value, steps.slice(1)) : null;
375
+ };
376
+ }
377
+ function fieldOf(value, steps) {
378
+ if (steps.length === 0) {
379
+ return typeof value === "string" ? value : null;
380
+ }
381
+ if (steps.length > 1) {
382
+ return null;
383
+ }
384
+ const field = asRecord(value)?.[steps[0]];
385
+ return typeof field === "string" ? field : null;
386
+ }
387
+ function substituted(value, settle) {
388
+ const fill = (text) => replaceInterpolations(text, settle);
389
+ return mapStrings(value, fill);
390
+ }
391
+ function mentions(value, iterators) {
392
+ const mentionsOne = (text) => interpolatedReferences(text).some(
393
+ (reference) => iterators.includes(reference.split(".")[0] ?? "")
394
+ );
395
+ return someString(value, mentionsOne);
396
+ }
397
+
47
398
  // src/filterQuery.ts
48
399
  var OPERATORS = ["!=", ">=", "<=", "=", ">", "<", ":"];
49
400
  var KEYWORDS = /* @__PURE__ */ new Set(["AND", "OR", "NOT"]);
@@ -305,96 +656,147 @@ var TokenReader = class {
305
656
  }
306
657
  };
307
658
 
308
- // src/references.ts
309
- var SUB_TOKEN = /\$\{([^}]*)\}/g;
310
- var RESOURCE_ATTRIBUTE = /^([A-Za-z][\w-]*)\.([A-Za-z_][\w-]*)\.([A-Za-z_][\w-]*)$/;
311
- var LOCAL_VALUE = /^local\.([A-Za-z_][\w-]*)$/;
312
- var LOCALS = "local";
313
- var CHAIN_LIMIT = 4;
314
- function referenceScope(resources, locals = []) {
315
- const scope = /* @__PURE__ */ new Map();
316
- for (const [resourceType, label, body] of resources) {
317
- const address = `${resourceType}.${label}`;
318
- if (!scope.has(address)) {
319
- scope.set(address, body);
320
- }
659
+ // src/jsonAttribute.ts
660
+ var JSON_ENCODE = /^\$\{\s*jsonencode\((.*)\)\s*\}$/s;
661
+ function jsonAttributeValue(value) {
662
+ if (typeof value !== "string") {
663
+ return null;
321
664
  }
322
- const stated = {};
323
- for (const block of locals) {
324
- for (const [name, value] of Object.entries(block)) {
325
- if (!(name in stated)) {
326
- stated[name] = value;
327
- }
328
- }
665
+ const literal = parseJson(value);
666
+ if (literal !== null) {
667
+ return literal;
329
668
  }
330
- scope.set(LOCALS, stated);
331
- return scope;
332
- }
333
- function resolveReferences(value, scope) {
334
- return expand(value, scope, []) ?? value;
335
- }
336
- function expand(value, scope, resolving) {
337
- let cycled = false;
338
- const expanded = value.replace(SUB_TOKEN, (written, inner) => {
339
- const reference = inner.trim();
340
- if (resolving.includes(reference)) {
341
- cycled = true;
342
- return written;
343
- }
344
- const stated = statedValue(reference, scope);
345
- if (stated === null || resolving.length >= CHAIN_LIMIT) {
346
- return written;
347
- }
348
- const nested = expand(stated, scope, [...resolving, reference]);
349
- if (nested === null) {
350
- cycled = true;
351
- return written;
352
- }
353
- return nested;
354
- });
355
- return cycled ? null : expanded;
669
+ const encoded = JSON_ENCODE.exec(value);
670
+ return encoded === null ? null : parseHclExpression(encoded[1]);
356
671
  }
357
- function statedValue(reference, scope) {
358
- const local = LOCAL_VALUE.exec(reference);
359
- if (local !== null) {
360
- const stated2 = scope.get(LOCALS)?.[local[1]];
361
- return typeof stated2 === "string" ? stated2 : null;
362
- }
363
- const parsed = RESOURCE_ATTRIBUTE.exec(reference);
364
- if (parsed === null) {
672
+ function parseJson(text) {
673
+ try {
674
+ return JSON.parse(text);
675
+ } catch {
365
676
  return null;
366
677
  }
367
- const [, resourceType, label, attribute] = parsed;
368
- const stated = scope.get(`${resourceType}.${label}`)?.[attribute];
369
- return typeof stated === "string" ? stated : null;
370
678
  }
371
679
 
372
680
  // src/index.ts
373
681
  function terraformToSummaries(source, sourceFile, options) {
374
- return summariesForFiles([{ source, sourceFile }], options);
682
+ return moduleSummaries(
683
+ moduleOf({
684
+ files: [{ source, sourceFile }],
685
+ directory: import_node_path.default.dirname(sourceFile),
686
+ namePrefix: "",
687
+ arguments: {},
688
+ ancestors: []
689
+ }),
690
+ options
691
+ );
375
692
  }
376
693
  function terraformFileToSummaries(target, options) {
377
- const files = import_node_fs.default.statSync(target).isDirectory() ? import_node_fs.default.readdirSync(target).filter((name) => name.endsWith(".tf")).map((name) => import_node_path.default.join(target, name)) : [target];
378
- return summariesForFiles(
379
- files.map((file) => ({
380
- source: import_node_fs.default.readFileSync(file, "utf8"),
381
- sourceFile: file
382
- })),
694
+ const directory = import_node_fs.default.statSync(target).isDirectory() ? target : import_node_path.default.dirname(target);
695
+ const files = import_node_fs.default.statSync(target).isDirectory() ? sourceFilesIn(target) : [readSourceFile(target)];
696
+ return moduleSummaries(
697
+ moduleOf({
698
+ files,
699
+ directory,
700
+ namePrefix: "",
701
+ arguments: {},
702
+ ancestors: [directory]
703
+ }),
383
704
  options
384
705
  );
385
706
  }
386
- function summariesForFiles(files, options) {
387
- const parsed = files.map((file) => parseSource(file)).filter((file) => file !== null);
388
- const scope = referenceScope(
389
- parsed.flatMap((file) => file.resources),
390
- parsed.flatMap((file) => file.locals)
391
- );
707
+ var LOCAL_SOURCE = /^\.\.?[/\\]/;
708
+ var MODULE_SOURCE = "source";
709
+ var MODULE_DEPTH_LIMIT = 8;
710
+ function sourceFilesIn(directory) {
711
+ return import_node_fs.default.readdirSync(directory).filter((name) => name.endsWith(".tf")).map((name) => readSourceFile(import_node_path.default.join(directory, name)));
712
+ }
713
+ function readSourceFile(file) {
714
+ return { source: import_node_fs.default.readFileSync(file, "utf8"), sourceFile: file };
715
+ }
716
+ function moduleOf(opts) {
717
+ const files = opts.files.map((file) => parseSource(file)).filter((file) => file !== null);
718
+ const scope = referenceScope({
719
+ resources: files.flatMap((file) => file.resources),
720
+ locals: files.flatMap((file) => file.locals),
721
+ defaults: Object.assign({}, ...files.map((file) => file.defaults)),
722
+ arguments: opts.arguments,
723
+ namePrefix: opts.namePrefix
724
+ });
725
+ const children = [];
726
+ for (const [label, call] of files.flatMap((file) => file.modules)) {
727
+ const child = childModule({ ...opts, scope, label, call });
728
+ if (child === null) {
729
+ continue;
730
+ }
731
+ children.push(child);
732
+ scope.resources.set(`module.${label}`, outputValues(child));
733
+ }
734
+ return { files, scope, children };
735
+ }
736
+ function childModule(opts) {
737
+ const source = stringOf(opts.call[MODULE_SOURCE]);
738
+ if (source === null || !LOCAL_SOURCE.test(source)) {
739
+ return null;
740
+ }
741
+ const directory = import_node_path.default.resolve(opts.directory, source);
742
+ if (opts.ancestors.includes(directory) || opts.ancestors.length >= MODULE_DEPTH_LIMIT || !isDirectory(directory)) {
743
+ return null;
744
+ }
745
+ return moduleOf({
746
+ files: sourceFilesIn(directory),
747
+ directory,
748
+ namePrefix: `${opts.namePrefix}module.${opts.label}.`,
749
+ arguments: passedArguments(opts.call, opts.scope),
750
+ ancestors: [...opts.ancestors, directory]
751
+ });
752
+ }
753
+ function isDirectory(target) {
754
+ return import_node_fs.default.existsSync(target) && import_node_fs.default.statSync(target).isDirectory();
755
+ }
756
+ function passedArguments(call, parent) {
757
+ const passed = {};
758
+ for (const [name, value] of Object.entries(call)) {
759
+ if (name === MODULE_SOURCE) {
760
+ continue;
761
+ }
762
+ const stated = asRecord(value);
763
+ if (stated !== null) {
764
+ passed[name] = resolvedThroughout(stated, parent);
765
+ continue;
766
+ }
767
+ const written = stringOf(value);
768
+ const settled = written === null ? null : resolveReferences(written, parent);
769
+ if (settled !== null && !settled.includes("${")) {
770
+ passed[name] = settled;
771
+ }
772
+ }
773
+ return passed;
774
+ }
775
+ function resolvedThroughout(value, scope) {
776
+ const resolveHere = (text) => resolveReferences(text, scope);
777
+ return mapStrings(value, resolveHere);
778
+ }
779
+ function outputValues(child) {
780
+ const stated = {};
781
+ for (const [name, value] of child.files.flatMap((file) => file.outputs)) {
782
+ const written = stringOf(value);
783
+ if (written === null || name in stated) {
784
+ continue;
785
+ }
786
+ const settled = resolveReferences(written, child.scope);
787
+ if (!settled.includes("${")) {
788
+ stated[name] = settled;
789
+ }
790
+ }
791
+ return stated;
792
+ }
793
+ function moduleSummaries(module2, options) {
392
794
  const summaries = [];
393
- for (const file of parsed) {
795
+ for (const file of module2.files) {
394
796
  for (const [resourceType, label, body] of file.resources) {
395
797
  for (const pack of options.packs) {
396
- const pattern = patternFor(pack, resourceType, file.constraints);
397
- if (pattern === void 0 || !entryApplies(pattern, body)) {
798
+ const pattern = patternFor(pack, resourceType, file.constraints, body);
799
+ if (pattern === void 0) {
398
800
  continue;
399
801
  }
400
802
  summaries.push(
@@ -404,23 +806,20 @@ function summariesForFiles(files, options) {
404
806
  body,
405
807
  sourceFile: file.sourceFile,
406
808
  resourceType,
407
- scope
809
+ scope: module2.scope,
810
+ options
408
811
  })
409
812
  );
410
813
  }
411
814
  }
412
815
  }
816
+ for (const child of module2.children) {
817
+ summaries.push(...moduleSummaries(child, options));
818
+ }
413
819
  return summaries;
414
820
  }
415
821
  function parseSource(file) {
416
- let read;
417
- try {
418
- const [parsed] = import_hcl2_parser.default.parseToObject(file.source);
419
- read = parsed;
420
- } catch {
421
- return null;
422
- }
423
- const document = asRecord(read);
822
+ const document = parseHclDocument(file.source);
424
823
  if (document === null) {
425
824
  return null;
426
825
  }
@@ -428,9 +827,43 @@ function parseSource(file) {
428
827
  sourceFile: file.sourceFile,
429
828
  constraints: providerConstraints(document),
430
829
  resources: resourcesIn(document),
431
- locals: localsIn(document)
830
+ locals: localsIn(document),
831
+ defaults: variableDefaults(document),
832
+ modules: labelledBlocks(document.module),
833
+ outputs: labelledBlocks(document.output).map(([name, block]) => [
834
+ name,
835
+ block.value
836
+ ])
432
837
  };
433
838
  }
839
+ function labelledBlocks(declared) {
840
+ const found = [];
841
+ for (const group of arrayOf(declared)) {
842
+ for (const [label, bodies] of Object.entries(asRecord(group) ?? {})) {
843
+ for (const body of arrayOf(bodies)) {
844
+ const read = asRecord(body);
845
+ if (read !== null) {
846
+ found.push([label, read]);
847
+ }
848
+ }
849
+ }
850
+ }
851
+ return found;
852
+ }
853
+ function variableDefaults(document) {
854
+ const defaults = {};
855
+ for (const group of arrayOf(document.variable)) {
856
+ for (const [name, declared] of Object.entries(asRecord(group) ?? {})) {
857
+ for (const block of arrayOf(declared)) {
858
+ const stated = asRecord(block)?.default;
859
+ if (stated !== void 0 && !(name in defaults)) {
860
+ defaults[name] = stated;
861
+ }
862
+ }
863
+ }
864
+ }
865
+ return defaults;
866
+ }
434
867
  function providerConstraints(document) {
435
868
  const constraints = /* @__PURE__ */ new Map();
436
869
  for (const block of arrayOf(document.terraform)) {
@@ -451,23 +884,21 @@ function providerConstraints(document) {
451
884
  }
452
885
  return constraints;
453
886
  }
454
- function patternFor(pack, resourceType, constraints) {
887
+ function patternFor(pack, resourceType, constraints, body) {
455
888
  const pinned = constraints.get(pack.provider);
456
- return pack.resources.find((pattern) => {
457
- if (pattern.resource !== resourceType) {
458
- return false;
459
- }
460
- if (pinned === void 0) {
461
- return true;
462
- }
463
- try {
464
- return import_semver.default.intersects(pinned, pattern.providerVersions, {
465
- loose: true
466
- });
467
- } catch {
468
- return true;
469
- }
470
- });
889
+ return pack.resources.find(
890
+ (pattern) => pattern.resource === resourceType && versionAllows(pinned, pattern.providerVersions) && entryApplies(pattern, body)
891
+ );
892
+ }
893
+ function versionAllows(pinned, providerVersions) {
894
+ if (pinned === void 0) {
895
+ return true;
896
+ }
897
+ try {
898
+ return import_semver.default.intersects(pinned, providerVersions, { loose: true });
899
+ } catch {
900
+ return true;
901
+ }
471
902
  }
472
903
  function entryApplies(pattern, body) {
473
904
  const gate = pattern.appliesWhen;
@@ -479,7 +910,7 @@ function entryApplies(pattern, body) {
479
910
  return gate.whenUnset === "read";
480
911
  }
481
912
  const text = stringOf(value);
482
- return text !== null && gate.equals.includes(text);
913
+ return text !== null && (gate.equals ?? []).includes(text);
483
914
  }
484
915
  var READERS = {
485
916
  storage: (site, boundary) => {
@@ -490,7 +921,8 @@ var READERS = {
490
921
  },
491
922
  "message-bus": (site, boundary) => [busSummary({ ...site, boundary })],
492
923
  metric: (site, boundary) => [metricSummary({ ...site, boundary })],
493
- "metric-reading": (site, boundary) => readingSummaries({ ...site, boundary })
924
+ "metric-reading": (site, boundary) => readingSummaries({ ...site, boundary }),
925
+ deployable: (site, boundary) => deployableSummaries({ ...site, boundary })
494
926
  };
495
927
  function summariesFor(opts) {
496
928
  const { pattern, ...site } = opts;
@@ -499,9 +931,11 @@ function summariesFor(opts) {
499
931
  }
500
932
  function storageSummary(opts) {
501
933
  const { boundary, label, shape, types } = opts;
502
- const address = `${opts.resourceType}.${label}`;
934
+ const address = `${opts.scope.namePrefix}${opts.resourceType}.${label}`;
935
+ const storageSystem = storageSystemOf(opts.body, boundary, opts.scope);
503
936
  const declaresContainer = boundary.declares !== "store";
504
937
  const physicalTable = !declaresContainer || boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
938
+ const listedFields = jsonFields(opts.body, boundary);
505
939
  return {
506
940
  kind: "library",
507
941
  location: {
@@ -514,35 +948,102 @@ function storageSummary(opts) {
514
948
  exportPath: null,
515
949
  boundaryBinding: (0, import_behavioral_ir.storageBinding)({
516
950
  recognition: "terraform",
517
- storageSystem: boundary.storageSystem,
951
+ storageSystem,
518
952
  ...boundary.transport !== void 0 ? { transport: boundary.transport } : {},
519
- scope: "default",
953
+ scope: containerScope(opts.body, boundary, opts.scope),
520
954
  container: declaresContainer ? label : null,
521
955
  accessPath: shape.accessPath
522
956
  })
523
957
  },
524
958
  inputs: [],
525
959
  transitions: [],
526
- gaps: [],
960
+ gaps: unknownEngineGaps(storageSystem, boundary),
527
961
  confidence: { source: "declared", level: "high" },
528
962
  metadata: {
529
963
  storageContract: {
530
- // A way in that copies part of an item has every field it will
531
- // ever have, whatever the container itself stores.
532
- fieldSet: shape.serves === null ? boundary.fieldSet : "exhaustive",
533
- ...boundary.identifies === void 0 ? {} : {
534
- identifies: { kind: "keyFields", fields: shape.keyFields },
535
- fields: (shape.serves ?? shape.keyFields).map((field) => ({
536
- name: field,
537
- ...types.has(field) ? { type: types.get(field) } : {},
538
- ...shape.keyFields.includes(field) ? { primary: true } : {}
539
- }))
540
- },
964
+ fieldSet: declaredFieldSet(boundary, shape, listedFields),
965
+ ...contractFields(boundary, shape, types, listedFields),
541
966
  ...physicalTable !== null ? { physicalTable } : {}
542
967
  }
543
968
  }
544
969
  };
545
970
  }
971
+ function storageSystemOf(body, boundary, scope) {
972
+ const spec = boundary.storageSystem;
973
+ if (typeof spec === "string") {
974
+ return spec;
975
+ }
976
+ return meaningOf(body, spec, scope) ?? null;
977
+ }
978
+ function unknownEngineGaps(storageSystem, boundary) {
979
+ const spec = boundary.storageSystem;
980
+ if (storageSystem !== null || typeof spec === "string") {
981
+ return [];
982
+ }
983
+ return [
984
+ {
985
+ type: "unreadOutcome",
986
+ conditions: [],
987
+ consequence: "unknown",
988
+ description: `"${spec.attribute}" states an engine this run could not settle, so which store this is stays unknown and it pairs with an access on any engine.`
989
+ }
990
+ ];
991
+ }
992
+ function contractFields(boundary, shape, types, listedFields) {
993
+ if (boundary.identifies !== void 0) {
994
+ return {
995
+ identifies: { kind: "keyFields", fields: shape.keyFields },
996
+ fields: (shape.serves ?? shape.keyFields).map((field) => ({
997
+ name: field,
998
+ ...types.has(field) ? { type: types.get(field) } : {},
999
+ ...shape.keyFields.includes(field) ? { primary: true } : {}
1000
+ }))
1001
+ };
1002
+ }
1003
+ return listedFields === null ? {} : { fields: listedFields };
1004
+ }
1005
+ function declaredFieldSet(boundary, shape, listedFields) {
1006
+ if (shape.serves !== null) {
1007
+ return "exhaustive";
1008
+ }
1009
+ return listedFields === null ? boundary.fieldSet : "exhaustive";
1010
+ }
1011
+ function jsonFields(body, boundary) {
1012
+ const spec = boundary.fieldsFromJson;
1013
+ if (spec === void 0) {
1014
+ return null;
1015
+ }
1016
+ const stated = jsonAttributeValue(valueAt(body, spec.attribute));
1017
+ if (!Array.isArray(stated)) {
1018
+ return null;
1019
+ }
1020
+ const fields = stated.flatMap((entry) => {
1021
+ const field = asRecord(entry);
1022
+ const name = field === null ? null : stringOf(field[spec.nameKey]);
1023
+ if (field === null || name === null) {
1024
+ return [];
1025
+ }
1026
+ const type = spec.typeKey === void 0 ? null : stringOf(field[spec.typeKey]);
1027
+ return [
1028
+ {
1029
+ name,
1030
+ ...type !== null ? { type } : {},
1031
+ ...spec.requires === void 0 ? {} : { nullable: !alwaysSet(field, spec.requires) }
1032
+ }
1033
+ ];
1034
+ });
1035
+ return fields.length === 0 ? null : fields;
1036
+ }
1037
+ function alwaysSet(field, requires) {
1038
+ const stated = stringOf(field[requires.key]);
1039
+ return stated !== null && requires.values.includes(stated);
1040
+ }
1041
+ function containerScope(body, boundary, scope) {
1042
+ if (boundary.scopeAttribute === void 0) {
1043
+ return "default";
1044
+ }
1045
+ return namePattern(valueAt(body, boundary.scopeAttribute), scope) ?? "default";
1046
+ }
546
1047
  function busSummary(opts) {
547
1048
  const { boundary, label } = opts;
548
1049
  const physicalName = boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
@@ -554,7 +1055,7 @@ function busSummary(opts) {
554
1055
  exportName: null
555
1056
  },
556
1057
  identity: {
557
- name: `${opts.resourceType}.${label}`,
1058
+ name: `${opts.scope.namePrefix}${opts.resourceType}.${label}`,
558
1059
  exportPath: null,
559
1060
  boundaryBinding: (0, import_behavioral_ir.messageBusBinding)({
560
1061
  recognition: "terraform",
@@ -575,14 +1076,26 @@ function busSummary(opts) {
575
1076
  }
576
1077
  };
577
1078
  }
578
- var NAME_HOLE = "{name}";
1079
+ var TEMPLATE_HOLE = /\{([^{}]+)\}/g;
1080
+ function metricTypeFrom(template, body, scope) {
1081
+ let missing = false;
1082
+ const spelled = template.replace(TEMPLATE_HOLE, (_whole, path2) => {
1083
+ const stated = namePattern(valueAt(body, path2), scope);
1084
+ if (stated === null) {
1085
+ missing = true;
1086
+ return "";
1087
+ }
1088
+ return stated;
1089
+ });
1090
+ return missing ? null : spelled;
1091
+ }
579
1092
  function metricSummary(opts) {
580
1093
  const { boundary } = opts;
581
- const declaredName = namePattern(
582
- opts.body[boundary.nameAttribute],
1094
+ const metricType = metricTypeFrom(
1095
+ boundary.metricTypeTemplate,
1096
+ opts.body,
583
1097
  opts.scope
584
1098
  );
585
- const metricType = declaredName === null ? null : boundary.metricTypeTemplate.replace(NAME_HOLE, declaredName);
586
1099
  return {
587
1100
  kind: "library",
588
1101
  location: {
@@ -591,7 +1104,7 @@ function metricSummary(opts) {
591
1104
  exportName: null
592
1105
  },
593
1106
  identity: {
594
- name: `${opts.resourceType}.${opts.label}`,
1107
+ name: `${opts.scope.namePrefix}${opts.resourceType}.${opts.label}`,
595
1108
  exportPath: null,
596
1109
  boundaryBinding: (0, import_behavioral_ir.metricBinding)({
597
1110
  recognition: "terraform",
@@ -603,12 +1116,14 @@ function metricSummary(opts) {
603
1116
  transitions: [],
604
1117
  gaps: [],
605
1118
  confidence: { source: "declared", level: "high" },
606
- metadata: { metricContract: metricContract(opts.body, boundary) }
1119
+ metadata: {
1120
+ metricContract: metricContract(opts.body, boundary, opts.scope)
1121
+ }
607
1122
  };
608
1123
  }
609
- function metricContract(body, boundary) {
610
- const values = meaningOf(body, boundary.values);
611
- const accumulates = meaningOf(body, boundary.accumulates);
1124
+ function metricContract(body, boundary, scope) {
1125
+ const values = meaningOf(body, boundary.values, scope);
1126
+ const accumulates = meaningOf(body, boundary.accumulates, scope);
612
1127
  return {
613
1128
  ...values !== void 0 ? { values } : {},
614
1129
  ...accumulates !== void 0 ? { accumulates } : {}
@@ -617,11 +1132,14 @@ function metricContract(body, boundary) {
617
1132
  function readingSummaries(opts) {
618
1133
  const { boundary } = opts;
619
1134
  const summaries = [];
620
- for (const reading of blocksAt(opts.body, boundary.readingBlocks)) {
621
- const query = stringOf(valueAt(reading, boundary.queryAttribute));
622
- for (const metricType of metricTypesIn(
623
- query,
624
- boundary.queryIdentityKey,
1135
+ for (const reading of blocksAt(
1136
+ opts.body,
1137
+ boundary.readingBlocks,
1138
+ opts.scope
1139
+ )) {
1140
+ for (const metricType of metricsRead(
1141
+ reading,
1142
+ boundary.identifies,
625
1143
  opts.scope
626
1144
  )) {
627
1145
  summaries.push({
@@ -632,7 +1150,7 @@ function readingSummaries(opts) {
632
1150
  exportName: null
633
1151
  },
634
1152
  identity: {
635
- name: `${opts.resourceType}.${opts.label}#${summaries.length}`,
1153
+ name: `${opts.scope.namePrefix}${opts.resourceType}.${opts.label}#${summaries.length}`,
636
1154
  exportPath: null,
637
1155
  boundaryBinding: (0, import_behavioral_ir.metricBinding)({
638
1156
  recognition: "terraform",
@@ -644,12 +1162,28 @@ function readingSummaries(opts) {
644
1162
  transitions: [],
645
1163
  gaps: [],
646
1164
  confidence: { source: "declared", level: "high" },
647
- metadata: { metricReading: metricReading(reading, boundary) }
1165
+ metadata: {
1166
+ metricReading: metricReading(reading, boundary, opts.scope)
1167
+ }
648
1168
  });
649
1169
  }
650
1170
  }
651
1171
  return summaries;
652
1172
  }
1173
+ var READING_IDENTITIES = {
1174
+ attributes: (reading, identity, scope) => [
1175
+ metricTypeFrom(identity.template, reading, scope)
1176
+ ],
1177
+ query: (reading, identity, scope) => metricTypesIn(
1178
+ stringOf(valueAt(reading, identity.attribute)),
1179
+ identity.key,
1180
+ scope
1181
+ )
1182
+ };
1183
+ function metricsRead(reading, identity, scope) {
1184
+ const read = READING_IDENTITIES[identity.from];
1185
+ return read(reading, identity, scope);
1186
+ }
653
1187
  function metricTypesIn(query, identityKey, scope) {
654
1188
  if (query === null) {
655
1189
  return [null];
@@ -661,23 +1195,212 @@ function metricTypesIn(query, identityKey, scope) {
661
1195
  const found = filterValuesFor(parsed.query, identityKey).map((value) => namePattern(value, scope)).filter((value) => value !== null);
662
1196
  return found.length === 0 ? [null] : found;
663
1197
  }
664
- function metricReading(reading, boundary) {
1198
+ function metricReading(reading, boundary, scope) {
665
1199
  const compares = boundary.comparesTo;
666
1200
  const comparesTo = compares !== void 0 && valueAt(reading, compares.attribute) !== void 0 ? compares.whenSet : void 0;
667
1201
  const reduces = boundary.reducesTo;
668
- const reducesTo = meaningOf(reading, reduces);
1202
+ const reducesTo = meaningOf(reading, reduces, scope);
669
1203
  return {
670
1204
  ...comparesTo !== void 0 ? { comparesTo } : {},
671
1205
  ...reducesTo !== void 0 ? { reducesTo } : {},
672
1206
  ...reduces === void 0 ? {} : { reduction: { setting: reduces.attribute, leaves: reduces.means } }
673
1207
  };
674
1208
  }
675
- function meaningOf(body, spec) {
1209
+ function deployableSummaries(opts) {
1210
+ const { boundary, label } = opts;
1211
+ return deployedProcesses(opts.body, boundary, opts.scope).map(
1212
+ (process) => deployableSummary({
1213
+ ...opts,
1214
+ boundary,
1215
+ process,
1216
+ instanceName: process.containerName === null ? label : (0, import_behavioral_ir.ecsContainerInstanceName)(label, process.containerName)
1217
+ })
1218
+ );
1219
+ }
1220
+ function deployableSummary(opts) {
1221
+ const { boundary, instanceName, process } = opts;
1222
+ const declared = declaredEnv(process.body, boundary.env ?? [], opts.scope);
1223
+ const platform = boundary.platformEnvVars ?? import_behavioral_ir.PLATFORM_INJECTED_ENV_VARS[boundary.deploymentTarget];
1224
+ const code = codePointer(process.body, boundary, opts.scope);
1225
+ const image = attributePattern(
1226
+ process.body,
1227
+ boundary.code?.imageAttribute,
1228
+ opts.scope
1229
+ );
1230
+ const runtime = attributePattern(
1231
+ process.body,
1232
+ boundary.runtimeAttribute,
1233
+ opts.scope
1234
+ );
1235
+ const prefix = opts.scope.namePrefix;
1236
+ const deployableUnit = {
1237
+ deploymentTarget: boundary.deploymentTarget,
1238
+ instanceName: `${prefix}${instanceName}`
1239
+ };
1240
+ return {
1241
+ kind: "library",
1242
+ location: {
1243
+ file: opts.sourceFile,
1244
+ range: { start: 1, end: 1 },
1245
+ exportName: null
1246
+ },
1247
+ identity: {
1248
+ name: `${prefix}${opts.resourceType}.${instanceName.replace("/", "#")}`,
1249
+ exportPath: null,
1250
+ boundaryBinding: (0, import_behavioral_ir.runtimeConfigBinding)({
1251
+ recognition: "terraform",
1252
+ ...deployableUnit
1253
+ }),
1254
+ deployableUnit
1255
+ },
1256
+ inputs: [],
1257
+ transitions: [],
1258
+ gaps: [],
1259
+ confidence: { source: "declared", level: "high" },
1260
+ metadata: (0, import_behavioral_ir.withRuntimeContractMetadata)(
1261
+ {
1262
+ codeScope: declaredCodeScope(
1263
+ opts.options.codeScopes?.[deployableUnit.instanceName],
1264
+ code.entry
1265
+ )
1266
+ },
1267
+ {
1268
+ envVars: [.../* @__PURE__ */ new Set([...declared.names, ...platform])].sort(),
1269
+ envVarSources: envVarSources(declared.names, platform),
1270
+ ...Object.keys(declared.targets).length > 0 ? { envVarTargets: declared.targets } : {},
1271
+ ...Object.keys(declared.values).length > 0 ? { envVarValues: declared.values } : {},
1272
+ ...code.entryPoint !== null ? { entryPoint: code.entryPoint } : {},
1273
+ ...image !== null ? { image } : {},
1274
+ ...runtime !== null ? { runtime } : {}
1275
+ }
1276
+ )
1277
+ };
1278
+ }
1279
+ function declaredCodeScope(directory, entry) {
1280
+ const written = entry !== null ? { entry } : {};
1281
+ if (directory !== void 0) {
1282
+ return { kind: "codeUri", path: directory, ...written };
1283
+ }
1284
+ return { kind: "unknown", ...written };
1285
+ }
1286
+ function envVarSources(declared, platform) {
1287
+ const sources = {};
1288
+ for (const name of declared) {
1289
+ sources[name] = "template";
1290
+ }
1291
+ for (const name of platform) {
1292
+ sources[name] ??= "platform";
1293
+ }
1294
+ return sources;
1295
+ }
1296
+ function deployedProcesses(body, boundary, scope) {
1297
+ const containers = boundary.containers;
1298
+ if (containers === void 0) {
1299
+ return [{ body, containerName: null }];
1300
+ }
1301
+ return blocksAt(body, containers.blocks, scope).map((container) => ({
1302
+ body: container,
1303
+ containerName: containers.nameAttribute === void 0 ? null : stringOf(container[containers.nameAttribute])
1304
+ }));
1305
+ }
1306
+ var HANDLER_MODULE = {
1307
+ "module.export": (written) => (0, import_behavioral_ir.parseHandler)(written)?.modulePath ?? null,
1308
+ name: () => null
1309
+ };
1310
+ function codePointer(body, boundary, scope) {
1311
+ const spec = boundary.code?.handler;
1312
+ const written = attributePattern(body, spec?.attribute, scope);
1313
+ if (spec === void 0 || written === null) {
1314
+ return { entryPoint: null, entry: null };
1315
+ }
1316
+ return {
1317
+ entryPoint: written,
1318
+ entry: (0, import_behavioral_ir.hasNameHole)(written) ? null : HANDLER_MODULE[spec.spelling](written)
1319
+ };
1320
+ }
1321
+ var ENV_READERS = {
1322
+ map: (body, declaration, into, scope) => {
1323
+ const stated = asRecord(valueAt(body, declaration.attribute)) ?? {};
1324
+ for (const [name, value] of Object.entries(stated)) {
1325
+ setVariable(into, name, stringOf(value), scope);
1326
+ }
1327
+ },
1328
+ entries: (body, declaration, into, scope) => {
1329
+ for (const entry of blocksAt(body, declaration.block.split("."), scope)) {
1330
+ const name = stringOf(entry[declaration.nameAttribute]);
1331
+ if (name === null) {
1332
+ continue;
1333
+ }
1334
+ setVariable(
1335
+ into,
1336
+ name,
1337
+ attributeText(entry, declaration.valueAttribute),
1338
+ scope
1339
+ );
1340
+ setTarget(
1341
+ into,
1342
+ name,
1343
+ attributeText(entry, declaration.secretAttribute),
1344
+ scope
1345
+ );
1346
+ }
1347
+ }
1348
+ };
1349
+ function declaredEnv(body, declarations, scope) {
1350
+ const declared = { names: [], values: {}, targets: {} };
1351
+ for (const declaration of declarations) {
1352
+ const read = ENV_READERS[declaration.style];
1353
+ read(body, declaration, declared, scope);
1354
+ }
1355
+ return declared;
1356
+ }
1357
+ function setVariable(into, name, written, scope) {
1358
+ if (!into.names.includes(name)) {
1359
+ into.names.push(name);
1360
+ }
1361
+ if (written === null) {
1362
+ return;
1363
+ }
1364
+ const pattern = namePattern(written, scope);
1365
+ if (pattern !== null) {
1366
+ into.values[name] = pattern;
1367
+ }
1368
+ setTarget(into, name, written, scope);
1369
+ }
1370
+ function setTarget(into, name, written, scope) {
1371
+ const target = written === null ? null : referencedResource(written, scope);
1372
+ if (target !== null) {
1373
+ into.targets[name] = { kind: "ref", logicalId: target };
1374
+ }
1375
+ }
1376
+ function attributeText(body, attribute) {
1377
+ return attribute === void 0 ? null : stringOf(valueAt(body, attribute));
1378
+ }
1379
+ function attributePattern(body, attribute, scope) {
1380
+ return attribute === void 0 ? null : namePattern(valueAt(body, attribute), scope);
1381
+ }
1382
+ var MEANING_KEYS = {
1383
+ value: (stated, keys) => keys.find((key) => key === stated),
1384
+ prefix: (stated, keys) => keys.find((key) => stated.startsWith(key))
1385
+ };
1386
+ function meaningOf(body, spec, scope) {
676
1387
  if (spec === void 0) {
677
1388
  return void 0;
678
1389
  }
679
- const stated = stringOf(valueAt(body, spec.attribute));
680
- return stated === null ? void 0 : spec.means[stated];
1390
+ const value = valueAt(body, spec.attribute);
1391
+ if (value === void 0) {
1392
+ return spec.whenUnset;
1393
+ }
1394
+ const written = stringOf(value);
1395
+ const stated = written === null ? null : resolveReferences(written, scope);
1396
+ if (stated === null || stated.includes("${")) {
1397
+ return void 0;
1398
+ }
1399
+ const key = MEANING_KEYS[spec.matches ?? "value"](
1400
+ stated,
1401
+ Object.keys(spec.means)
1402
+ );
1403
+ return key === void 0 ? void 0 : spec.means[key];
681
1404
  }
682
1405
  function valueAt(body, path2) {
683
1406
  const steps = path2.split(".");
@@ -692,15 +1415,24 @@ function valueAt(body, path2) {
692
1415
  }
693
1416
  return current[last];
694
1417
  }
695
- function blocksAt(body, blocks) {
1418
+ function blocksAt(body, blocks, scope) {
696
1419
  let found = [body];
697
1420
  for (const block of blocks) {
698
- found = found.flatMap(
699
- (record) => arrayOf(record[block]).map(asRecord).filter((nested) => nested !== null)
700
- );
1421
+ found = found.flatMap((record) => [
1422
+ ...nestedRecords(record[block], scope),
1423
+ ...dynamicBlocks(record, block, scope)
1424
+ ]);
701
1425
  }
702
1426
  return found;
703
1427
  }
1428
+ function nestedRecords(value, scope) {
1429
+ const iterated = iteratedRecords(value, scope);
1430
+ if (iterated !== null) {
1431
+ return iterated;
1432
+ }
1433
+ const stated = typeof value === "string" ? jsonAttributeValue(value) : value;
1434
+ return arrayOf(stated).map(asRecord).filter((nested) => nested !== null);
1435
+ }
704
1436
  function keyedShapes(body, boundary) {
705
1437
  if (boundary.identifies === void 0) {
706
1438
  return [{ accessPath: null, keyFields: [], serves: null }];
@@ -806,23 +1538,12 @@ function namePattern(value, scope) {
806
1538
  typeof value === "string" ? resolveReferences(value, scope) : value
807
1539
  );
808
1540
  }
809
- function arrayOf(value) {
810
- if (value === void 0 || value === null) {
811
- return [];
812
- }
813
- return Array.isArray(value) ? value : [value];
814
- }
815
- function asRecord(value) {
816
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
817
- }
818
- function stringOf(value) {
819
- return typeof value === "string" ? value : null;
820
- }
821
1541
  // Annotate the CommonJS export names for ESM import in node:
822
1542
  0 && (module.exports = {
823
1543
  filterCalls,
824
1544
  filterTerms,
825
1545
  filterValuesFor,
1546
+ jsonAttributeValue,
826
1547
  parseFilterQuery,
827
1548
  terraformFileToSummaries,
828
1549
  terraformToSummaries