@dotzen/dotzen 0.0.1

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.
@@ -0,0 +1,16 @@
1
+ import { NormalizedResource } from './model';
2
+ /** hcl2json emits `{ resource: { type: { name: [block, ...] } } }`. */
3
+ export interface Hcl2JsonRoot {
4
+ resource?: Record<string, Record<string, unknown[]>>;
5
+ variable?: Record<string, unknown[]>;
6
+ locals?: unknown[];
7
+ }
8
+ /** Resolved `var.*` / `local.*` values, keyed by reference, raw form. */
9
+ export type Scope = Map<string, unknown>;
10
+ /** Collect `variable` defaults and `locals` from all parsed files. */
11
+ export declare function buildScope(roots: Hcl2JsonRoot[]): Scope;
12
+ /**
13
+ * Adapter boundary (doc 06): parser output -> dotzen's own model.
14
+ * The engine never sees `Hcl2JsonRoot`. `scope` resolves var/local refs.
15
+ */
16
+ export declare function normalize(parsed: Hcl2JsonRoot, file: string, rawText: string, scope?: Scope, environmentOverride?: string): NormalizedResource[];
@@ -0,0 +1,462 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildScope = buildScope;
4
+ exports.normalize = normalize;
5
+ const vocabulary_1 = require("../vocabulary");
6
+ const KNOWN_TYPES = new Set([
7
+ ...Object.values(vocabulary_1.AwsResource),
8
+ ...Object.values(vocabulary_1.AzureResource),
9
+ ...Object.values(vocabulary_1.GcpResource),
10
+ ]);
11
+ const isInterpolated = (s) => s.includes('${');
12
+ const asObject = (o) => o && typeof o === 'object' ? o : {};
13
+ function toValue(raw) {
14
+ if (typeof raw === 'string' && isInterpolated(raw))
15
+ return { kind: 'unresolved', expr: raw };
16
+ if (typeof raw === 'string' ||
17
+ typeof raw === 'number' ||
18
+ typeof raw === 'boolean')
19
+ return { kind: 'literal', value: raw };
20
+ return { kind: 'unresolved', expr: JSON.stringify(raw) };
21
+ }
22
+ // A value that is *exactly* one `var.x` / `local.y` reference — the only
23
+ // form we resolve. Compound interpolations (`"a-${var.x}"`) stay unresolved.
24
+ const SOLE_REF = /^\$\{(var|local)\.([A-Za-z0-9_-]+)\}$/;
25
+ /**
26
+ * Resolve a raw value against the scope. A sole `var`/`local` reference is
27
+ * followed (through local→var chains, depth-bounded) to its literal; a
28
+ * reference with no known value, or any non-sole-reference expression,
29
+ * stays unresolved — which correctly yields "could not evaluate".
30
+ */
31
+ function resolveValue(raw, scope, depth = 8) {
32
+ if (typeof raw === 'string') {
33
+ const m = SOLE_REF.exec(raw);
34
+ if (m) {
35
+ const key = `${m[1]}.${m[2]}`;
36
+ if (depth > 0 && scope.has(key))
37
+ return resolveValue(scope.get(key), scope, depth - 1);
38
+ return { kind: 'unresolved', expr: raw };
39
+ }
40
+ }
41
+ return toValue(raw);
42
+ }
43
+ /** Collect `variable` defaults and `locals` from all parsed files. */
44
+ function buildScope(roots) {
45
+ const scope = new Map();
46
+ for (const root of roots) {
47
+ for (const [name, blocks] of Object.entries(root.variable ?? {})) {
48
+ const b = asObject(Array.isArray(blocks) ? blocks[0] : undefined);
49
+ if ('default' in b)
50
+ scope.set(`var.${name}`, b.default);
51
+ }
52
+ if (Array.isArray(root.locals)) {
53
+ for (const block of root.locals) {
54
+ for (const [k, v] of Object.entries(asObject(block)))
55
+ scope.set(`local.${k}`, v);
56
+ }
57
+ }
58
+ }
59
+ return scope;
60
+ }
61
+ function mapIngressObj(o, scope) {
62
+ const oo = asObject(o);
63
+ const cidrs = Array.isArray(oo.cidr_blocks) ? oo.cidr_blocks : [];
64
+ return {
65
+ fromPort: resolveValue(oo.from_port, scope),
66
+ toPort: resolveValue(oo.to_port, scope),
67
+ cidrBlocks: cidrs.map((c) => resolveValue(c, scope)),
68
+ };
69
+ }
70
+ /** Inline `<name> { ... }` blocks (name is 'ingress' or 'egress'). */
71
+ function inlineBlocks(block, name, scope) {
72
+ const raw = block[name];
73
+ return Array.isArray(raw) ? raw.map((i) => mapIngressObj(i, scope)) : [];
74
+ }
75
+ /** Follow a sole var/local reference to its raw resolved value (or undefined). */
76
+ function resolveRaw(raw, scope, depth = 8) {
77
+ if (typeof raw === 'string') {
78
+ const m = SOLE_REF.exec(raw);
79
+ if (m) {
80
+ const key = `${m[1]}.${m[2]}`;
81
+ if (depth > 0 && scope.has(key))
82
+ return resolveRaw(scope.get(key), scope, depth - 1);
83
+ return undefined;
84
+ }
85
+ if (isInterpolated(raw))
86
+ return undefined; // compound expr / function call
87
+ }
88
+ return raw;
89
+ }
90
+ /** Substitute `<iterator>.value[.field]` references with the element value. */
91
+ function substituteValue(v, iterator, el) {
92
+ if (typeof v === 'string') {
93
+ const it = escapeRegExp(iterator);
94
+ // eslint-disable-next-line security/detect-non-literal-regexp -- iterator escaped
95
+ const field = new RegExp(`^\\$\\{${it}\\.value\\.([A-Za-z0-9_-]+)\\}$`).exec(v);
96
+ if (field)
97
+ return asObject(el)[field[1]];
98
+ if (v === `\${${iterator}.value}`)
99
+ return el;
100
+ return v;
101
+ }
102
+ if (Array.isArray(v))
103
+ return v.map((x) => substituteValue(x, iterator, el));
104
+ return v;
105
+ }
106
+ const substituteIterator = (content, iterator, el) => {
107
+ const out = {};
108
+ for (const [k, v] of Object.entries(content))
109
+ out[k] = substituteValue(v, iterator, el);
110
+ return out;
111
+ };
112
+ /**
113
+ * `dynamic "ingress" { content { ... } }` blocks. When the `for_each`
114
+ * collection resolves (via scope) to a concrete list/map of literals, the
115
+ * block is EXPANDED — one ingress per element, with `<iterator>.value`
116
+ * references substituted — so it yields a definite verdict. When the
117
+ * collection cannot be resolved (a var without default, a `toset(...)` or
118
+ * other function call, `each.*`), the content is kept as-is with its
119
+ * values unresolved, correctly yielding "could not evaluate".
120
+ */
121
+ function dynamicBlocks(block, name, scope) {
122
+ const dyn = block.dynamic;
123
+ if (!dyn || typeof dyn !== 'object')
124
+ return [];
125
+ const ing = dyn[name];
126
+ if (!Array.isArray(ing))
127
+ return [];
128
+ const out = [];
129
+ for (const d of ing) {
130
+ const dobj = asObject(d);
131
+ const contents = Array.isArray(dobj.content) ? dobj.content : [];
132
+ const iterator = typeof dobj.iterator === 'string' ? dobj.iterator : name;
133
+ const collection = resolveRaw(dobj.for_each, scope);
134
+ const elements = Array.isArray(collection)
135
+ ? collection
136
+ : collection && typeof collection === 'object'
137
+ ? Object.values(collection)
138
+ : undefined;
139
+ if (elements) {
140
+ for (const el of elements)
141
+ for (const c of contents)
142
+ out.push(mapIngressObj(substituteIterator(asObject(c), iterator, el), scope));
143
+ }
144
+ else {
145
+ for (const c of contents)
146
+ out.push(mapIngressObj(c, scope));
147
+ }
148
+ }
149
+ return out;
150
+ }
151
+ /** A standalone `aws_vpc_security_group_ingress_rule` is itself one rule. */
152
+ function ruleResourceIngress(block, scope) {
153
+ const cidrs = [];
154
+ if (block.cidr_ipv4 !== undefined)
155
+ cidrs.push(block.cidr_ipv4);
156
+ if (block.cidr_ipv6 !== undefined)
157
+ cidrs.push(block.cidr_ipv6);
158
+ return [
159
+ {
160
+ fromPort: resolveValue(block.from_port, scope),
161
+ toPort: resolveValue(block.to_port, scope),
162
+ cidrBlocks: cidrs.map((c) => resolveValue(c, scope)),
163
+ },
164
+ ];
165
+ }
166
+ /** A literal (non-interpolated) string, or undefined. */
167
+ const litStr = (v) => typeof v === 'string' && !isInterpolated(v) ? v : undefined;
168
+ // Azure "any source" sentinels — all mean the public internet. Normalized
169
+ // to the CIDR the cloud-neutral `denyIngress` already recognizes.
170
+ const AZURE_ANY_SOURCE = new Set(['*', 'Internet', '0.0.0.0/0']);
171
+ /** Parse a port spec ("22", "*", "80-90") to a pair (Azure + GCP share this). */
172
+ function parsePortRange(r) {
173
+ if (r === '*')
174
+ return { from: 0, to: 65535 };
175
+ const dash = r.split('-');
176
+ if (dash.length === 2) {
177
+ const a = Number(dash[0]);
178
+ const b = Number(dash[1]);
179
+ if (Number.isFinite(a) && Number.isFinite(b))
180
+ return { from: a, to: b };
181
+ return undefined;
182
+ }
183
+ const n = Number(r);
184
+ return Number.isFinite(n) ? { from: n, to: n } : undefined;
185
+ }
186
+ /**
187
+ * Map one Azure NSG rule to the cloud-neutral ingress model. Only
188
+ * `Inbound` + `Allow` rules are ingress; a `*`/`Internet` source becomes
189
+ * `0.0.0.0/0` so the shared `denyIngress` condition works unchanged.
190
+ */
191
+ function azureRuleToIngress(o, scope) {
192
+ if (litStr(o.direction) !== 'Inbound' || litStr(o.access) !== 'Allow')
193
+ return [];
194
+ const srcRaw = o.source_address_prefixes ?? o.source_address_prefix;
195
+ const sources = (Array.isArray(srcRaw) ? srcRaw : [srcRaw]).filter((s) => s !== undefined);
196
+ const cidrBlocks = sources.map((s) => {
197
+ const v = resolveValue(s, scope);
198
+ return v.kind === 'literal' &&
199
+ typeof v.value === 'string' &&
200
+ AZURE_ANY_SOURCE.has(v.value)
201
+ ? { kind: 'literal', value: '0.0.0.0/0' }
202
+ : v;
203
+ });
204
+ const portRaw = o.destination_port_ranges ?? o.destination_port_range;
205
+ const ports = Array.isArray(portRaw) ? portRaw : [portRaw];
206
+ return ports.map((p) => {
207
+ const s = litStr(p);
208
+ const pair = s !== undefined ? parsePortRange(s) : undefined;
209
+ if (pair)
210
+ return {
211
+ fromPort: { kind: 'literal', value: pair.from },
212
+ toPort: { kind: 'literal', value: pair.to },
213
+ cidrBlocks,
214
+ };
215
+ const un = resolveValue(p, scope);
216
+ return { fromPort: un, toPort: un, cidrBlocks };
217
+ });
218
+ }
219
+ /**
220
+ * Map a `google_compute_firewall` to the cloud-neutral ingress model. Only
221
+ * INGRESS direction (the default) counts; each `allow { protocol, ports }`
222
+ * block becomes ingress rules over `source_ranges`. An allow block with no
223
+ * ports means every port (0–65535).
224
+ */
225
+ function gcpFirewallToIngress(block, scope) {
226
+ const direction = litStr(block.direction) ?? 'INGRESS';
227
+ if (direction !== 'INGRESS')
228
+ return [];
229
+ const srcRaw = Array.isArray(block.source_ranges) ? block.source_ranges : [];
230
+ const cidrBlocks = srcRaw.map((s) => resolveValue(s, scope));
231
+ const allows = Array.isArray(block.allow) ? block.allow : [];
232
+ const out = [];
233
+ for (const a of allows) {
234
+ const ao = asObject(a);
235
+ const ports = Array.isArray(ao.ports) ? ao.ports : [];
236
+ if (ports.length === 0) {
237
+ out.push({
238
+ fromPort: { kind: 'literal', value: 0 },
239
+ toPort: { kind: 'literal', value: 65535 },
240
+ cidrBlocks,
241
+ });
242
+ continue;
243
+ }
244
+ for (const p of ports) {
245
+ const s = litStr(p);
246
+ const pair = s !== undefined ? parsePortRange(s) : undefined;
247
+ if (pair)
248
+ out.push({
249
+ fromPort: { kind: 'literal', value: pair.from },
250
+ toPort: { kind: 'literal', value: pair.to },
251
+ cidrBlocks,
252
+ });
253
+ else {
254
+ const un = resolveValue(p, scope);
255
+ out.push({ fromPort: un, toPort: un, cidrBlocks });
256
+ }
257
+ }
258
+ }
259
+ return out;
260
+ }
261
+ function ingressFor(type, block, scope) {
262
+ if (!block)
263
+ return [];
264
+ if (type === vocabulary_1.AwsResource.VpcSecurityGroupIngressRule)
265
+ return ruleResourceIngress(block, scope);
266
+ if (type === vocabulary_1.AzureResource.NetworkSecurityRule)
267
+ return azureRuleToIngress(block, scope);
268
+ if (type === vocabulary_1.AzureResource.NetworkSecurityGroup) {
269
+ const rules = Array.isArray(block.security_rule) ? block.security_rule : [];
270
+ return rules.flatMap((r) => azureRuleToIngress(asObject(r), scope));
271
+ }
272
+ if (type === vocabulary_1.GcpResource.ComputeFirewall)
273
+ return gcpFirewallToIngress(block, scope);
274
+ return [
275
+ ...inlineBlocks(block, 'ingress', scope),
276
+ ...dynamicBlocks(block, 'ingress', scope),
277
+ ];
278
+ }
279
+ function egressFor(block, scope) {
280
+ if (!block)
281
+ return [];
282
+ return [
283
+ ...inlineBlocks(block, 'egress', scope),
284
+ ...dynamicBlocks(block, 'egress', scope),
285
+ ];
286
+ }
287
+ /**
288
+ * Tags: a literal map gives us the present keys; a `${...}` expression
289
+ * (var/merge/local) is unresolved; an absent block is resolved-but-empty
290
+ * (the literal AI-generated case — no tags written means none present).
291
+ */
292
+ function tagsOf(block) {
293
+ const t = block?.tags;
294
+ if (t === undefined)
295
+ return { kind: 'resolved', keys: [] };
296
+ if (typeof t === 'string' && isInterpolated(t))
297
+ return { kind: 'unresolved' };
298
+ if (t && typeof t === 'object' && !Array.isArray(t))
299
+ return { kind: 'resolved', keys: Object.keys(t) };
300
+ return { kind: 'unresolved' };
301
+ }
302
+ /** Resolved value of the `environment` tag (for rule scoping), if present. */
303
+ function environmentOf(block, scope) {
304
+ const t = block?.tags;
305
+ if (!t || typeof t !== 'object' || Array.isArray(t))
306
+ return undefined;
307
+ const env = t.environment;
308
+ return env === undefined ? undefined : resolveValue(env, scope);
309
+ }
310
+ // Blocks handled elsewhere (ingress/egress) or as tags — not attributes.
311
+ const NON_ATTR_BLOCKS = new Set(['ingress', 'egress', 'dynamic', 'tags']);
312
+ // hcl2json represents a nested block as an array of one object.
313
+ const isNestedBlock = (v) => Array.isArray(v) &&
314
+ v.length > 0 &&
315
+ typeof v[0] === 'object' &&
316
+ v[0] !== null &&
317
+ !Array.isArray(v[0]);
318
+ /**
319
+ * Extract scalar attributes and list-valued attributes from a block,
320
+ * recursing through nested blocks and flattening to dotted keys
321
+ * (`vpc_config { public_access_cidrs = [...] }` -> list
322
+ * `vpc_config.public_access_cidrs`; `metadata_options { http_tokens = x }`
323
+ * -> attribute `metadata_options.http_tokens`). Maps (tags) are skipped;
324
+ * ingress/egress/dynamic are handled elsewhere.
325
+ */
326
+ function collect(prefix, obj, scope, out) {
327
+ for (const [k, v] of Object.entries(obj)) {
328
+ if (v === null)
329
+ continue;
330
+ if (prefix === '' && NON_ATTR_BLOCKS.has(k))
331
+ continue;
332
+ const key = prefix ? `${prefix}.${k}` : k;
333
+ if (isNestedBlock(v)) {
334
+ out.blocks.push(key); // record the block path (even if empty)
335
+ collect(key, v[0], scope, out);
336
+ }
337
+ else if (Array.isArray(v)) {
338
+ out.lists[key] = {
339
+ kind: 'resolved',
340
+ items: v.map((x) => resolveValue(x, scope)),
341
+ };
342
+ }
343
+ else if (typeof v !== 'object') {
344
+ out.attributes[key] = resolveValue(v, scope);
345
+ }
346
+ }
347
+ }
348
+ function extractAttrs(block, scope) {
349
+ const out = { attributes: {}, lists: {}, blocks: [] };
350
+ if (block)
351
+ collect('', block, scope, out);
352
+ return out;
353
+ }
354
+ const toStrList = (v) => {
355
+ if (typeof v === 'string')
356
+ return [v];
357
+ if (Array.isArray(v))
358
+ return v.filter((x) => typeof x === 'string');
359
+ return [];
360
+ };
361
+ /**
362
+ * Parse an IAM `policy` argument. A literal JSON document (heredoc/inline
363
+ * string) is parsed into statements; a `jsonencode(...)` expression, a
364
+ * variable, or malformed JSON is `unresolved` (=> "could not evaluate").
365
+ */
366
+ function policyOf(block) {
367
+ const raw = block?.policy;
368
+ if (typeof raw !== 'string')
369
+ return undefined; // no inline JSON policy
370
+ if (isInterpolated(raw))
371
+ return { kind: 'unresolved' }; // jsonencode/var/...
372
+ let doc;
373
+ try {
374
+ doc = JSON.parse(raw);
375
+ }
376
+ catch {
377
+ return { kind: 'unresolved' };
378
+ }
379
+ const stmtRaw = asObject(doc).Statement;
380
+ const list = Array.isArray(stmtRaw) ? stmtRaw : stmtRaw ? [stmtRaw] : [];
381
+ const statements = list.map((s) => {
382
+ const so = asObject(s);
383
+ return {
384
+ effect: typeof so.Effect === 'string' ? so.Effect : '',
385
+ actions: toStrList(so.Action),
386
+ resources: toStrList(so.Resource),
387
+ notActions: toStrList(so.NotAction),
388
+ };
389
+ });
390
+ return { kind: 'parsed', statements };
391
+ }
392
+ /** Parse ECS `container_definitions` (a literal-JSON array of containers). */
393
+ function containersOf(block) {
394
+ const raw = block?.container_definitions;
395
+ if (typeof raw !== 'string')
396
+ return undefined;
397
+ if (isInterpolated(raw))
398
+ return { kind: 'unresolved' }; // jsonencode/var
399
+ let doc;
400
+ try {
401
+ doc = JSON.parse(raw);
402
+ }
403
+ catch {
404
+ return { kind: 'unresolved' };
405
+ }
406
+ const arr = Array.isArray(doc) ? doc : [];
407
+ const containers = arr.map((c) => {
408
+ const co = asObject(c);
409
+ return {
410
+ name: typeof co.name === 'string' ? co.name : '',
411
+ privileged: co.privileged === true,
412
+ };
413
+ });
414
+ return { kind: 'parsed', containers };
415
+ }
416
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
417
+ /** Best-effort line of a `resource "type" "name"` block via text scan. */
418
+ function findLine(text, type, name) {
419
+ const lines = text.split(/\r?\n/);
420
+ // eslint-disable-next-line security/detect-non-literal-regexp -- inputs escaped above
421
+ const needle = new RegExp(`resource\\s+"${escapeRegExp(type)}"\\s+"${escapeRegExp(name)}"`);
422
+ for (let i = 0; i < lines.length; i++) {
423
+ if (needle.test(lines[i] ?? ''))
424
+ return i + 1;
425
+ }
426
+ return 1;
427
+ }
428
+ /**
429
+ * Adapter boundary (doc 06): parser output -> dotzen's own model.
430
+ * The engine never sees `Hcl2JsonRoot`. `scope` resolves var/local refs.
431
+ */
432
+ function normalize(parsed, file, rawText, scope = new Map(), environmentOverride) {
433
+ const out = [];
434
+ const byType = parsed.resource ?? {};
435
+ for (const [type, byName] of Object.entries(byType)) {
436
+ if (!KNOWN_TYPES.has(type))
437
+ continue;
438
+ for (const [name, blocks] of Object.entries(byName)) {
439
+ const block = (Array.isArray(blocks) ? blocks[0] : blocks);
440
+ const extracted = extractAttrs(block, scope);
441
+ out.push({
442
+ type: type,
443
+ name,
444
+ file,
445
+ line: findLine(rawText, type, name),
446
+ ingress: ingressFor(type, block, scope),
447
+ egress: egressFor(block, scope),
448
+ tags: tagsOf(block),
449
+ attributes: extracted.attributes,
450
+ lists: extracted.lists,
451
+ blocks: extracted.blocks,
452
+ policy: policyOf(block),
453
+ containers: containersOf(block),
454
+ // A root's declared environment wins over the resource's own tag.
455
+ environment: environmentOverride !== undefined
456
+ ? { kind: 'literal', value: environmentOverride }
457
+ : environmentOf(block, scope),
458
+ });
459
+ }
460
+ }
461
+ return out;
462
+ }
@@ -0,0 +1,13 @@
1
+ import { Result } from '../result/result';
2
+ import { DotzenError } from '../result/errors';
3
+ import { NormalizedResource } from './model';
4
+ /**
5
+ * Read a terraform directory, parse each .tf via the official parser
6
+ * (hcl2json / WASM), and normalize into dotzen's model. Async because
7
+ * the WASM parser is async. Reported file paths are made relative to
8
+ * `projectRoot` (defaults to `dir`) so output is readable and portable —
9
+ * and, for multi-root layouts, shows which root each finding came from.
10
+ * A single `parseTf` call builds ONE scope, so calling it once per root
11
+ * keeps each root's `var`/`local` values isolated.
12
+ */
13
+ export declare function parseTf(dir: string, projectRoot?: string, environmentOverride?: string): Promise<Result<NormalizedResource[], DotzenError>>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseTf = parseTf;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const hcl2json_1 = require("@cdktf/hcl2json");
40
+ const result_1 = require("../result/result");
41
+ const normalize_1 = require("./normalize");
42
+ function findTfFiles(dir) {
43
+ const entries = fs.readdirSync(dir, { recursive: true });
44
+ return entries.filter((e) => e.endsWith('.tf')).map((e) => path.join(dir, e));
45
+ }
46
+ const toPosix = (p) => p.split(path.sep).join('/');
47
+ /**
48
+ * Read a terraform directory, parse each .tf via the official parser
49
+ * (hcl2json / WASM), and normalize into dotzen's model. Async because
50
+ * the WASM parser is async. Reported file paths are made relative to
51
+ * `projectRoot` (defaults to `dir`) so output is readable and portable —
52
+ * and, for multi-root layouts, shows which root each finding came from.
53
+ * A single `parseTf` call builds ONE scope, so calling it once per root
54
+ * keeps each root's `var`/`local` values isolated.
55
+ */
56
+ async function parseTf(dir, projectRoot = dir, environmentOverride) {
57
+ if (!fs.existsSync(dir))
58
+ return (0, result_1.err)({ kind: 'PathNotFound', path: dir });
59
+ const files = findTfFiles(dir);
60
+ // Pass 1: parse every file (variables/locals may live in a different
61
+ // file from the resources that reference them).
62
+ const parsedFiles = [];
63
+ for (const file of files) {
64
+ const text = fs.readFileSync(file, 'utf8');
65
+ try {
66
+ const parsed = (await (0, hcl2json_1.parse)(path.basename(file), text));
67
+ parsedFiles.push({ file, text, parsed });
68
+ }
69
+ catch (e) {
70
+ return (0, result_1.err)({
71
+ kind: 'ParseFailed',
72
+ file,
73
+ detail: e instanceof Error ? e.message : String(e),
74
+ });
75
+ }
76
+ }
77
+ // Pass 2: build the cross-file var/local scope, then normalize with it.
78
+ const scope = (0, normalize_1.buildScope)(parsedFiles.map((p) => p.parsed));
79
+ const resources = [];
80
+ for (const { file, text, parsed } of parsedFiles) {
81
+ const rel = toPosix(path.relative(projectRoot, file));
82
+ resources.push(...(0, normalize_1.normalize)(parsed, rel, text, scope, environmentOverride));
83
+ }
84
+ return (0, result_1.ok)(resources);
85
+ }
@@ -0,0 +1,3 @@
1
+ /** Public DSL surface — what `.zen/spec.ts` imports. */
2
+ export { rule, RuleBuilder } from './spec/rule';
3
+ export { AwsResource, Port, Cidr, Effect, Tag, AwsAttribute, Acl, Environment, Approver, HttpTokens, EksLogType, TlsPolicy, Protocol, Block, Wildcard, ApiGatewayAuthorization, AzureResource, AzureAttribute, StorageTlsVersion, SqlTlsVersion, BuiltInRole, NetworkDefaultAction, GcpResource, GcpAttribute, PublicAccessPreventionMode, IamMember, PrimitiveRole, OauthScope, SqlSslMode, } from './vocabulary';
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqlSslMode = exports.OauthScope = exports.PrimitiveRole = exports.IamMember = exports.PublicAccessPreventionMode = exports.GcpAttribute = exports.GcpResource = exports.NetworkDefaultAction = exports.BuiltInRole = exports.SqlTlsVersion = exports.StorageTlsVersion = exports.AzureAttribute = exports.AzureResource = exports.ApiGatewayAuthorization = exports.Wildcard = exports.Block = exports.Protocol = exports.TlsPolicy = exports.EksLogType = exports.HttpTokens = exports.Approver = exports.Environment = exports.Acl = exports.AwsAttribute = exports.Tag = exports.Effect = exports.Cidr = exports.Port = exports.AwsResource = exports.RuleBuilder = exports.rule = void 0;
4
+ /** Public DSL surface — what `.zen/spec.ts` imports. */
5
+ var rule_1 = require("./spec/rule");
6
+ Object.defineProperty(exports, "rule", { enumerable: true, get: function () { return rule_1.rule; } });
7
+ Object.defineProperty(exports, "RuleBuilder", { enumerable: true, get: function () { return rule_1.RuleBuilder; } });
8
+ var vocabulary_1 = require("./vocabulary");
9
+ Object.defineProperty(exports, "AwsResource", { enumerable: true, get: function () { return vocabulary_1.AwsResource; } });
10
+ Object.defineProperty(exports, "Port", { enumerable: true, get: function () { return vocabulary_1.Port; } });
11
+ Object.defineProperty(exports, "Cidr", { enumerable: true, get: function () { return vocabulary_1.Cidr; } });
12
+ Object.defineProperty(exports, "Effect", { enumerable: true, get: function () { return vocabulary_1.Effect; } });
13
+ Object.defineProperty(exports, "Tag", { enumerable: true, get: function () { return vocabulary_1.Tag; } });
14
+ Object.defineProperty(exports, "AwsAttribute", { enumerable: true, get: function () { return vocabulary_1.AwsAttribute; } });
15
+ Object.defineProperty(exports, "Acl", { enumerable: true, get: function () { return vocabulary_1.Acl; } });
16
+ Object.defineProperty(exports, "Environment", { enumerable: true, get: function () { return vocabulary_1.Environment; } });
17
+ Object.defineProperty(exports, "Approver", { enumerable: true, get: function () { return vocabulary_1.Approver; } });
18
+ Object.defineProperty(exports, "HttpTokens", { enumerable: true, get: function () { return vocabulary_1.HttpTokens; } });
19
+ Object.defineProperty(exports, "EksLogType", { enumerable: true, get: function () { return vocabulary_1.EksLogType; } });
20
+ Object.defineProperty(exports, "TlsPolicy", { enumerable: true, get: function () { return vocabulary_1.TlsPolicy; } });
21
+ Object.defineProperty(exports, "Protocol", { enumerable: true, get: function () { return vocabulary_1.Protocol; } });
22
+ Object.defineProperty(exports, "Block", { enumerable: true, get: function () { return vocabulary_1.Block; } });
23
+ Object.defineProperty(exports, "Wildcard", { enumerable: true, get: function () { return vocabulary_1.Wildcard; } });
24
+ Object.defineProperty(exports, "ApiGatewayAuthorization", { enumerable: true, get: function () { return vocabulary_1.ApiGatewayAuthorization; } });
25
+ Object.defineProperty(exports, "AzureResource", { enumerable: true, get: function () { return vocabulary_1.AzureResource; } });
26
+ Object.defineProperty(exports, "AzureAttribute", { enumerable: true, get: function () { return vocabulary_1.AzureAttribute; } });
27
+ Object.defineProperty(exports, "StorageTlsVersion", { enumerable: true, get: function () { return vocabulary_1.StorageTlsVersion; } });
28
+ Object.defineProperty(exports, "SqlTlsVersion", { enumerable: true, get: function () { return vocabulary_1.SqlTlsVersion; } });
29
+ Object.defineProperty(exports, "BuiltInRole", { enumerable: true, get: function () { return vocabulary_1.BuiltInRole; } });
30
+ Object.defineProperty(exports, "NetworkDefaultAction", { enumerable: true, get: function () { return vocabulary_1.NetworkDefaultAction; } });
31
+ Object.defineProperty(exports, "GcpResource", { enumerable: true, get: function () { return vocabulary_1.GcpResource; } });
32
+ Object.defineProperty(exports, "GcpAttribute", { enumerable: true, get: function () { return vocabulary_1.GcpAttribute; } });
33
+ Object.defineProperty(exports, "PublicAccessPreventionMode", { enumerable: true, get: function () { return vocabulary_1.PublicAccessPreventionMode; } });
34
+ Object.defineProperty(exports, "IamMember", { enumerable: true, get: function () { return vocabulary_1.IamMember; } });
35
+ Object.defineProperty(exports, "PrimitiveRole", { enumerable: true, get: function () { return vocabulary_1.PrimitiveRole; } });
36
+ Object.defineProperty(exports, "OauthScope", { enumerable: true, get: function () { return vocabulary_1.OauthScope; } });
37
+ Object.defineProperty(exports, "SqlSslMode", { enumerable: true, get: function () { return vocabulary_1.SqlSslMode; } });
@@ -0,0 +1,18 @@
1
+ import { CheckReport } from '../engine/evaluate';
2
+ import { DotzenError } from '../result/errors';
3
+ /** A blocking violation fired (only Block fails the build). */
4
+ export declare const hasBlocking: (r: CheckReport) => boolean;
5
+ /** A require_approval rule fired — the pipeline must pause for sign-off. */
6
+ export declare const requiresApproval: (r: CheckReport) => boolean;
7
+ /**
8
+ * Only BLOCK violations fail the build (exit 1). Warnings and
9
+ * require_approval do not hard-fail — the pipeline proceeds (to a manual
10
+ * approval gate when approval is required). See doc 04.
11
+ */
12
+ export declare function reportExitCode(report: CheckReport): 0 | 1;
13
+ export declare function renderTerminal(report: CheckReport, opts?: {
14
+ color?: boolean;
15
+ }): string;
16
+ export declare function renderJson(report: CheckReport): string;
17
+ /** Exhaustive over DotzenError.kind (doc 06). */
18
+ export declare function renderError(error: DotzenError): string;