@dotzen/dotzen 0.1.0 → 0.1.2
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/hcl/normalize.js +89 -13
- package/dist/vocabulary/index.d.ts +1 -0
- package/dist/vocabulary/index.js +1 -0
- package/package.json +74 -74
package/dist/hcl/normalize.js
CHANGED
|
@@ -306,8 +306,55 @@ function egressFor(block, scope) {
|
|
|
306
306
|
// `=` (not `==`/`>=`/etc.) is HCL's attribute-assignment operator, so this
|
|
307
307
|
// reliably picks up flat map keys inside a `merge(...)` argument.
|
|
308
308
|
const OBJECT_KEY = /([A-Za-z_][A-Za-z0-9_-]*)\s*=(?!=)/g;
|
|
309
|
-
// `var.x` / `local.y`
|
|
310
|
-
|
|
309
|
+
// A whole argument that is exactly one `var.x` / `local.y` ref (optionally
|
|
310
|
+
// `${…}`-wrapped). Only such TOP-LEVEL merge args contribute tag keys — refs
|
|
311
|
+
// inside an object literal's *values* (e.g. `Ou = var.ou`) must not count.
|
|
312
|
+
const ARG_REF = /^\$?\{?\s*(var|local)\.([A-Za-z0-9_-]+)\s*\}?$/;
|
|
313
|
+
/** The substring inside the outermost `merge( … )`, or null if unbalanced. */
|
|
314
|
+
function mergeInner(value) {
|
|
315
|
+
const m = /^merge\s*\(/.exec(value);
|
|
316
|
+
if (!m)
|
|
317
|
+
return null;
|
|
318
|
+
let depth = 0;
|
|
319
|
+
const start = m[0].length;
|
|
320
|
+
for (let i = start; i < value.length; i++) {
|
|
321
|
+
const c = value[i];
|
|
322
|
+
if (c === '(')
|
|
323
|
+
depth++;
|
|
324
|
+
else if (c === ')') {
|
|
325
|
+
if (depth === 0)
|
|
326
|
+
return value.slice(start, i);
|
|
327
|
+
depth--;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
/** Split an argument list on top-level commas, respecting (), {}, [], quotes. */
|
|
333
|
+
function splitTopLevelArgs(inner) {
|
|
334
|
+
const args = [];
|
|
335
|
+
let depth = 0;
|
|
336
|
+
let start = 0;
|
|
337
|
+
let quote = null;
|
|
338
|
+
for (let i = 0; i < inner.length; i++) {
|
|
339
|
+
const c = inner[i];
|
|
340
|
+
if (quote) {
|
|
341
|
+
if (c === quote && inner[i - 1] !== '\\')
|
|
342
|
+
quote = null;
|
|
343
|
+
}
|
|
344
|
+
else if (c === '"' || c === "'")
|
|
345
|
+
quote = c;
|
|
346
|
+
else if (c === '(' || c === '{' || c === '[')
|
|
347
|
+
depth++;
|
|
348
|
+
else if (c === ')' || c === '}' || c === ']')
|
|
349
|
+
depth--;
|
|
350
|
+
else if (c === ',' && depth === 0) {
|
|
351
|
+
args.push(inner.slice(start, i));
|
|
352
|
+
start = i + 1;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
args.push(inner.slice(start));
|
|
356
|
+
return args.map((a) => a.trim()).filter((a) => a.length > 0);
|
|
357
|
+
}
|
|
311
358
|
/**
|
|
312
359
|
* Keys known to be present on a tags value, and whether that set is
|
|
313
360
|
* COMPLETE. Follows sole `var`/`local` references to their value, and
|
|
@@ -329,21 +376,50 @@ function tagKeys(value, scope, depth = 8) {
|
|
|
329
376
|
const key = `${ref[1]}.${ref[2]}`;
|
|
330
377
|
return scope.has(key) ? tagKeys(scope.get(key), scope, depth - 1) : null;
|
|
331
378
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
379
|
+
// Only treat `merge(...)` as the TOP-LEVEL call (strip a `${…}` wrapper
|
|
380
|
+
// first). merge nested inside another function is opaque → could-not-eval.
|
|
381
|
+
const stripped = value.replace(/^\$\{/, '').replace(/\}$/, '').trim();
|
|
382
|
+
if (/^merge\s*\(/.test(stripped)) {
|
|
383
|
+
const inner = mergeInner(stripped);
|
|
384
|
+
if (inner === null)
|
|
385
|
+
return null;
|
|
386
|
+
// merge() only ADDS keys, so the result is COMPLETE iff every top-level
|
|
387
|
+
// argument is fully knowable: an object literal, or a ref that resolves
|
|
388
|
+
// to a complete map. An unresolvable ref or an opaque expression (e.g. a
|
|
389
|
+
// function call) means more keys could appear → PARTIAL, so a missing
|
|
390
|
+
// required tag degrades to could-not-evaluate rather than a false claim.
|
|
391
|
+
const keys = new Set();
|
|
392
|
+
let complete = true;
|
|
393
|
+
for (const arg of splitTopLevelArgs(inner)) {
|
|
394
|
+
const refMatch = ARG_REF.exec(arg);
|
|
395
|
+
if (arg.startsWith('{')) {
|
|
396
|
+
// Object literal: keys are the `ident =` pairs (tag maps are flat);
|
|
397
|
+
// the values — which may themselves mention refs — are irrelevant.
|
|
398
|
+
for (const m of arg.match(OBJECT_KEY) ?? [])
|
|
399
|
+
keys.add(m.replace(/\s*=$/, ''));
|
|
400
|
+
}
|
|
401
|
+
else if (refMatch) {
|
|
402
|
+
const scopeKey = `${refMatch[1]}.${refMatch[2]}`;
|
|
403
|
+
const sub = depth > 0 && scope.has(scopeKey)
|
|
404
|
+
? tagKeys(scope.get(scopeKey), scope, depth - 1)
|
|
405
|
+
: null;
|
|
406
|
+
if (sub === null) {
|
|
407
|
+
complete = false; // unresolvable ref — could add unknown keys
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
// Proven-present keys count even from a partial sub (merge only
|
|
411
|
+
// adds); a partial sub still means the whole set is incomplete.
|
|
342
412
|
for (const k of sub.keys)
|
|
343
413
|
keys.add(k);
|
|
414
|
+
if (!sub.complete)
|
|
415
|
+
complete = false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
complete = false; // opaque arg (function call, etc.) may add keys
|
|
344
420
|
}
|
|
345
421
|
}
|
|
346
|
-
return { keys: [...keys], complete
|
|
422
|
+
return { keys: [...keys], complete };
|
|
347
423
|
}
|
|
348
424
|
return null; // some other unresolvable expression
|
|
349
425
|
}
|
|
@@ -32,6 +32,7 @@ export declare enum AwsResource {
|
|
|
32
32
|
EcsTaskDefinition = "aws_ecs_task_definition",
|
|
33
33
|
SecretsmanagerSecretVersion = "aws_secretsmanager_secret_version",
|
|
34
34
|
RdsCluster = "aws_rds_cluster",
|
|
35
|
+
RdsClusterInstance = "aws_rds_cluster_instance",
|
|
35
36
|
RedshiftCluster = "aws_redshift_cluster",
|
|
36
37
|
ElasticacheReplicationGroup = "aws_elasticache_replication_group",
|
|
37
38
|
S3BucketServerSideEncryptionConfiguration = "aws_s3_bucket_server_side_encryption_configuration",
|
package/dist/vocabulary/index.js
CHANGED
|
@@ -37,6 +37,7 @@ var AwsResource;
|
|
|
37
37
|
AwsResource["EcsTaskDefinition"] = "aws_ecs_task_definition";
|
|
38
38
|
AwsResource["SecretsmanagerSecretVersion"] = "aws_secretsmanager_secret_version";
|
|
39
39
|
AwsResource["RdsCluster"] = "aws_rds_cluster";
|
|
40
|
+
AwsResource["RdsClusterInstance"] = "aws_rds_cluster_instance";
|
|
40
41
|
AwsResource["RedshiftCluster"] = "aws_redshift_cluster";
|
|
41
42
|
AwsResource["ElasticacheReplicationGroup"] = "aws_elasticache_replication_group";
|
|
42
43
|
AwsResource["S3BucketServerSideEncryptionConfiguration"] = "aws_s3_bucket_server_side_encryption_configuration";
|
package/package.json
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dotzen/dotzen",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Prose as Code — governance for AI-generated Terraform (AWS, Azure, GCP).",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"terraform",
|
|
8
|
-
"hcl",
|
|
9
|
-
"governance",
|
|
10
|
-
"policy-as-code",
|
|
11
|
-
"iac",
|
|
12
|
-
"security",
|
|
13
|
-
"cis",
|
|
14
|
-
"compliance",
|
|
15
|
-
"aws",
|
|
16
|
-
"azure",
|
|
17
|
-
"gcp"
|
|
18
|
-
],
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://gitlab.com/governance-tools/dotzen.git",
|
|
22
|
-
"directory": "packages/cli"
|
|
23
|
-
},
|
|
24
|
-
"homepage": "https://gitlab.com/governance-tools/dotzen#readme",
|
|
25
|
-
"bugs": {
|
|
26
|
-
"url": "https://gitlab.com/governance-tools/dotzen/-/issues"
|
|
27
|
-
},
|
|
28
|
-
"bin": {
|
|
29
|
-
"dotzen": "./bin/dotzen.js"
|
|
30
|
-
},
|
|
31
|
-
"main": "./dist/index.js",
|
|
32
|
-
"types": "./dist/index.d.ts",
|
|
33
|
-
"exports": {
|
|
34
|
-
".": {
|
|
35
|
-
"types": "./dist/index.d.ts",
|
|
36
|
-
"default": "./dist/index.js"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
"files": [
|
|
40
|
-
"dist",
|
|
41
|
-
"bin"
|
|
42
|
-
],
|
|
43
|
-
"engines": {
|
|
44
|
-
"node": ">=18"
|
|
45
|
-
},
|
|
46
|
-
"publishConfig": {
|
|
47
|
-
"access": "public"
|
|
48
|
-
},
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "tsc -p tsconfig.build.json",
|
|
51
|
-
"typecheck": "tsc --noEmit",
|
|
52
|
-
"test": "vitest run src",
|
|
53
|
-
"test:integration": "vitest run tests",
|
|
54
|
-
"coverage": "vitest run --coverage",
|
|
55
|
-
"lint": "eslint .",
|
|
56
|
-
"format:check": "prettier --check .",
|
|
57
|
-
"format": "prettier --write .",
|
|
58
|
-
"prepublishOnly": "npm run build"
|
|
59
|
-
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"@cdktf/hcl2json": "^0.21.0",
|
|
62
|
-
"jiti": "^2.4.2"
|
|
63
|
-
},
|
|
64
|
-
"devDependencies": {
|
|
65
|
-
"@types/node": "^22.10.5",
|
|
66
|
-
"@vitest/coverage-v8": "^4.1.10",
|
|
67
|
-
"eslint": "^9.17.0",
|
|
68
|
-
"eslint-plugin-security": "^3.0.1",
|
|
69
|
-
"prettier": "^3.4.2",
|
|
70
|
-
"typescript": "^5.7.2",
|
|
71
|
-
"typescript-eslint": "^8.19.0",
|
|
72
|
-
"vitest": "^4.1.10"
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@dotzen/dotzen",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Prose as Code — governance for AI-generated Terraform (AWS, Azure, GCP).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"terraform",
|
|
8
|
+
"hcl",
|
|
9
|
+
"governance",
|
|
10
|
+
"policy-as-code",
|
|
11
|
+
"iac",
|
|
12
|
+
"security",
|
|
13
|
+
"cis",
|
|
14
|
+
"compliance",
|
|
15
|
+
"aws",
|
|
16
|
+
"azure",
|
|
17
|
+
"gcp"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://gitlab.com/governance-tools/dotzen.git",
|
|
22
|
+
"directory": "packages/cli"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://gitlab.com/governance-tools/dotzen#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://gitlab.com/governance-tools/dotzen/-/issues"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"dotzen": "./bin/dotzen.js"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"bin"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.build.json",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run src",
|
|
53
|
+
"test:integration": "vitest run tests",
|
|
54
|
+
"coverage": "vitest run --coverage",
|
|
55
|
+
"lint": "eslint .",
|
|
56
|
+
"format:check": "prettier --check .",
|
|
57
|
+
"format": "prettier --write .",
|
|
58
|
+
"prepublishOnly": "npm run build"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@cdktf/hcl2json": "^0.21.0",
|
|
62
|
+
"jiti": "^2.4.2"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^22.10.5",
|
|
66
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
67
|
+
"eslint": "^9.17.0",
|
|
68
|
+
"eslint-plugin-security": "^3.0.1",
|
|
69
|
+
"prettier": "^3.4.2",
|
|
70
|
+
"typescript": "^5.7.2",
|
|
71
|
+
"typescript-eslint": "^8.19.0",
|
|
72
|
+
"vitest": "^4.1.10"
|
|
73
|
+
}
|
|
74
|
+
}
|