@intentius/chant 0.124.44 → 0.124.46
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/capabilities/aws.paths +148 -0
- package/dist/capabilities/base-mappings.json +122 -0
- package/dist/capabilities/catalog.d.ts +89 -0
- package/dist/capabilities/catalog.d.ts.map +1 -0
- package/dist/capabilities/catalog.js +204 -0
- package/dist/capabilities/language.d.ts +103 -0
- package/dist/capabilities/language.d.ts.map +1 -0
- package/dist/capabilities/language.js +335 -0
- package/dist/capabilities/matcher.d.ts +65 -0
- package/dist/capabilities/matcher.d.ts.map +1 -0
- package/dist/capabilities/matcher.js +299 -0
- package/dist/capabilities/view.d.ts +207 -0
- package/dist/capabilities/view.d.ts.map +1 -0
- package/dist/capabilities/view.js +484 -0
- package/dist/ci-identity.d.ts +183 -12
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +362 -53
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +1 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +17 -1
- package/dist/cli/handlers/declared-graph.d.ts +44 -0
- package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
- package/dist/cli/handlers/declared-graph.js +87 -0
- package/dist/cli/handlers/graph.d.ts +25 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +93 -87
- package/dist/cli/handlers/pr-env.d.ts +8 -2
- package/dist/cli/handlers/pr-env.d.ts.map +1 -1
- package/dist/cli/handlers/pr-env.js +127 -225
- package/dist/cli/handlers/pr.d.ts +3 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +1 -0
- package/dist/cli/handlers/search-count.d.ts +89 -0
- package/dist/cli/handlers/search-count.d.ts.map +1 -0
- package/dist/cli/handlers/search-count.js +132 -0
- package/dist/cli/handlers/search.d.ts +358 -6
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +1065 -155
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +23 -3
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +3 -0
- package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
- package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
- package/dist/cli/mcp/tools/capabilities.js +27 -0
- package/dist/cli/mcp/workspace-tools.d.ts +3 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.js +47 -2
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +64 -17
- package/dist/cli/registry.d.ts +22 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +2 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +4 -2
- package/dist/components/fan-out-support.d.ts +3 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +1 -1
- package/dist/components/pr-env-ports.d.ts +29 -0
- package/dist/components/pr-env-ports.d.ts.map +1 -0
- package/dist/components/pr-env-ports.js +77 -0
- package/dist/components/pr-env-run.d.ts +254 -0
- package/dist/components/pr-env-run.d.ts.map +1 -0
- package/dist/components/pr-env-run.js +376 -0
- package/dist/components/pr-environment.d.ts +91 -15
- package/dist/components/pr-environment.d.ts.map +1 -1
- package/dist/components/pr-environment.js +95 -9
- package/dist/components/pr-pipeline.d.ts +10 -1
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/config-environments.d.ts +92 -0
- package/dist/config-environments.d.ts.map +1 -0
- package/dist/config-environments.js +102 -0
- package/dist/config.d.ts +24 -84
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -96
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +15 -3
- package/dist/graph-effective.d.ts +33 -2
- package/dist/graph-effective.d.ts.map +1 -1
- package/dist/graph-effective.js +215 -68
- package/dist/graph-ir.d.ts +23 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +130 -0
- package/dist/graph-sub-template.d.ts +11 -0
- package/dist/graph-sub-template.d.ts.map +1 -0
- package/dist/graph-sub-template.js +19 -0
- package/dist/lexicon.d.ts +9 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +28 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +23 -1
- package/dist/lint/rule.d.ts +6 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/live-endpoint.d.ts.map +1 -1
- package/dist/live-endpoint.js +1 -1
- package/dist/observation.d.ts +48 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +21 -5
- package/dist/op/activities/activity-contracts.d.ts +4 -4
- package/dist/op/op-waves.d.ts +17 -0
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +23 -1
- package/dist/pr-forge.d.ts +15 -1
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +28 -16
- package/dist/pr-requires.d.ts +5 -1
- package/dist/pr-requires.d.ts.map +1 -1
- package/dist/wave-runtime.mjs +251 -250
- package/dist/workspace/capabilities-contract.d.ts +66 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -0
- package/dist/workspace/capabilities-contract.js +107 -0
- package/dist/workspace/capabilities.schema.json +229 -0
- package/dist/workspace/ci-cloud-identity.d.ts +3 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +16 -1
- package/dist/workspace/ci-run.d.ts +35 -8
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +95 -21
- package/dist/workspace/ci-secrets.d.ts.map +1 -1
- package/dist/workspace/ci-secrets.js +4 -3
- package/dist/workspace/compose-graph.d.ts +9 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +3 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +39 -2
- package/dist/workspace/declaration.d.ts +10 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +17 -1
- package/dist/workspace/declaration.schema.json +36 -1
- package/dist/workspace/graph-cache.d.ts +8 -0
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +27 -3
- package/dist/workspace/graph-cli.d.ts +39 -2
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +66 -24
- package/dist/workspace/graph.schema.json +148 -0
- package/dist/workspace/kind-readers.d.ts +3 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +7 -1
- package/dist/workspace/member-commands.d.ts +10 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +17 -1
- package/dist/workspace/nested-graph.d.ts +2 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +32 -1
- package/dist/workspace/reason-codes.d.ts +10 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +12 -0
- package/dist/workspace/search-cli.d.ts +204 -0
- package/dist/workspace/search-cli.d.ts.map +1 -0
- package/dist/workspace/search-cli.js +466 -0
- package/dist/workspace/search-contract.d.ts +56 -0
- package/dist/workspace/search-contract.d.ts.map +1 -0
- package/dist/workspace/search-contract.js +70 -0
- package/dist/workspace/search.schema.json +822 -0
- package/package.json +1 -1
- package/src/__snapshots__/okf.test.ts.snap +10 -0
- package/src/capabilities/aws.paths +148 -0
- package/src/capabilities/base-mappings.json +122 -0
- package/src/capabilities/catalog.ts +230 -0
- package/src/capabilities/language.test.ts +191 -0
- package/src/capabilities/language.ts +373 -0
- package/src/capabilities/matcher.ts +315 -0
- package/src/capabilities/view-compose.test.ts +76 -0
- package/src/capabilities/view.ts +631 -0
- package/src/ci-identity-targets.test.ts +275 -0
- package/src/ci-identity.ts +394 -61
- package/src/cli/commands/lint.ts +1 -0
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/declared-graph.ts +99 -0
- package/src/cli/handlers/graph.test.ts +94 -3
- package/src/cli/handlers/graph.ts +123 -96
- package/src/cli/handlers/pr-env.ts +135 -227
- package/src/cli/handlers/pr.ts +4 -0
- package/src/cli/handlers/search-build-roots.test.ts +144 -0
- package/src/cli/handlers/search-count.test.ts +191 -0
- package/src/cli/handlers/search-count.ts +165 -0
- package/src/cli/handlers/search-json.test.ts +333 -0
- package/src/cli/handlers/search-live.test.ts +387 -0
- package/src/cli/handlers/search-paths.test.ts +235 -0
- package/src/cli/handlers/search.ts +1312 -166
- package/src/cli/main.test.ts +28 -3
- package/src/cli/main.ts +23 -3
- package/src/cli/mcp/server.test.ts +3 -3
- package/src/cli/mcp/server.ts +3 -0
- package/src/cli/mcp/tools/capabilities.ts +32 -0
- package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
- package/src/cli/mcp/workspace-tools.ts +47 -2
- package/src/cli/parse-args.ts +52 -13
- package/src/cli/registry.ts +22 -0
- package/src/components/cli-support.ts +6 -1
- package/src/components/fan-out-support.ts +4 -1
- package/src/components/pr-env-ports.ts +89 -0
- package/src/components/pr-env-run-bundle.test.ts +54 -0
- package/src/components/pr-env-run.test.ts +290 -0
- package/src/components/pr-env-run.ts +563 -0
- package/src/components/pr-environment.ts +161 -13
- package/src/components/pr-pipeline.ts +10 -1
- package/src/config-environments.ts +133 -0
- package/src/config.ts +37 -124
- package/src/graph-detail.ts +15 -3
- package/src/graph-effective.ts +226 -75
- package/src/graph-ir.test.ts +163 -0
- package/src/graph-ir.ts +147 -0
- package/src/graph-sub-template.ts +22 -0
- package/src/lexicon.ts +10 -0
- package/src/lifecycle/observe.test.ts +27 -1
- package/src/lifecycle/observe.ts +43 -1
- package/src/lint/rule.ts +6 -0
- package/src/live-endpoint.ts +1 -1
- package/src/observation.test.ts +25 -0
- package/src/observation.ts +69 -5
- package/src/op/op-waves.ts +38 -1
- package/src/pr-forge-bundle.test.ts +1 -1
- package/src/pr-forge.ts +44 -17
- package/src/pr-requires.test.ts +43 -6
- package/src/pr-requires.ts +5 -1
- package/src/workspace/capabilities-contract.test.ts +88 -0
- package/src/workspace/capabilities-contract.ts +132 -0
- package/src/workspace/capabilities.schema.json +229 -0
- package/src/workspace/ci-cloud-identity.test.ts +20 -0
- package/src/workspace/ci-cloud-identity.ts +14 -1
- package/src/workspace/ci-run.test.ts +74 -1
- package/src/workspace/ci-run.ts +122 -29
- package/src/workspace/ci-secrets.test.ts +1 -1
- package/src/workspace/ci-secrets.ts +4 -3
- package/src/workspace/compose-graph.ts +10 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
- package/src/workspace/conformance/index.ts +31 -2
- package/src/workspace/declaration.schema.json +36 -1
- package/src/workspace/declaration.test.ts +7 -0
- package/src/workspace/declaration.ts +24 -2
- package/src/workspace/graph-cache.ts +27 -3
- package/src/workspace/graph-cli.ts +96 -24
- package/src/workspace/graph.schema.json +148 -0
- package/src/workspace/kind-readers.e2e.test.ts +28 -0
- package/src/workspace/kind-readers.test.ts +1 -1
- package/src/workspace/kind-readers.ts +7 -1
- package/src/workspace/member-commands.ts +22 -1
- package/src/workspace/nested-graph.ts +32 -1
- package/src/workspace/reason-codes.test.ts +7 -0
- package/src/workspace/reason-codes.ts +12 -0
- package/src/workspace/search-cli.e2e.test.ts +305 -0
- package/src/workspace/search-cli.ts +591 -0
- package/src/workspace/search-contract.ts +95 -0
- package/src/workspace/search.schema.json +822 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { terraformType } from "../graph-effective.js";
|
|
2
|
+
/**
|
|
3
|
+
* Merge mappings for the same lexicon: objects merge key by key at every
|
|
4
|
+
* depth, lists concatenate, and anything else in a later mapping wins.
|
|
5
|
+
*/
|
|
6
|
+
export function mergeMappings(...ms) {
|
|
7
|
+
const merge = (a, b) => {
|
|
8
|
+
if (b === undefined)
|
|
9
|
+
return a;
|
|
10
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
11
|
+
return [...a, ...b.filter((x) => typeof x === "object" || !a.includes(x))];
|
|
12
|
+
const isObj = (x) => !!x && typeof x === "object" && !Array.isArray(x);
|
|
13
|
+
if (isObj(a) && isObj(b)) {
|
|
14
|
+
const out = { ...a };
|
|
15
|
+
for (const [k, v] of Object.entries(b))
|
|
16
|
+
out[k] = merge(a[k], v);
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
return b;
|
|
20
|
+
};
|
|
21
|
+
return ms.reduce((acc, m) => merge(acc, m), { lexicon: ms[0]?.lexicon ?? "" });
|
|
22
|
+
}
|
|
23
|
+
const arr = (v) => (v == null ? [] : Array.isArray(v) ? v : [v]);
|
|
24
|
+
/** Every value at `path` (`A.B`, `A[].B`), flattened. */
|
|
25
|
+
function readPath(obj, path) {
|
|
26
|
+
let cur = [obj];
|
|
27
|
+
for (const seg of path.split(".")) {
|
|
28
|
+
const spread = seg.endsWith("[]");
|
|
29
|
+
const key = spread ? seg.slice(0, -2) : seg;
|
|
30
|
+
const next = [];
|
|
31
|
+
for (const c of cur) {
|
|
32
|
+
if (!c || typeof c !== "object")
|
|
33
|
+
continue;
|
|
34
|
+
const v = c[key];
|
|
35
|
+
if (v === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
if (spread)
|
|
38
|
+
next.push(...arr(v));
|
|
39
|
+
else
|
|
40
|
+
next.push(v);
|
|
41
|
+
}
|
|
42
|
+
cur = next;
|
|
43
|
+
}
|
|
44
|
+
return cur;
|
|
45
|
+
}
|
|
46
|
+
function readSource(obj, src) {
|
|
47
|
+
if (typeof src === "string")
|
|
48
|
+
return readPath(obj, src)[0];
|
|
49
|
+
if (Array.isArray(src)) {
|
|
50
|
+
for (const p of src) {
|
|
51
|
+
const v = readPath(obj, p)[0];
|
|
52
|
+
if (v !== undefined && v !== null)
|
|
53
|
+
return v;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
if ("const" in src)
|
|
58
|
+
return src.const;
|
|
59
|
+
return src.all.flatMap((p) => readPath(obj, p).flatMap(arr));
|
|
60
|
+
}
|
|
61
|
+
function matchesWhen(obj, when) {
|
|
62
|
+
if (!when)
|
|
63
|
+
return true;
|
|
64
|
+
return Object.entries(when).every(([k, v]) => readPath(obj, k)[0] === v);
|
|
65
|
+
}
|
|
66
|
+
/** The bare name a row prints: no stack qualification, no root directory. */
|
|
67
|
+
export function displayName(id) {
|
|
68
|
+
const s = id.includes("::") ? id.slice(id.lastIndexOf("::") + 2) : id;
|
|
69
|
+
return s.slice(s.lastIndexOf("/") + 1);
|
|
70
|
+
}
|
|
71
|
+
const ARN_NAMES = [
|
|
72
|
+
{ label: "Role", re: /^arn:aws[\w-]*:iam::[^:]*:role\/(?:[^*]*\/)?([^/*]+)$/ },
|
|
73
|
+
{ label: "Bucket", re: /^arn:aws[\w-]*:s3:::([^/*]+)(?:\/.*)?$/ },
|
|
74
|
+
{ label: "Table", re: /^arn:aws[\w-]*:dynamodb:[^:]*:[^:]*:table\/([^/*]+)$/ },
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* Normalize what each lexicon spells its own way, for labels whose meaning is
|
|
78
|
+
* core vocabulary: an ingress rule's protocol and port range.
|
|
79
|
+
*/
|
|
80
|
+
function normalize(label, attrs) {
|
|
81
|
+
if (label === "IngressRule") {
|
|
82
|
+
const raw = attrs.proto == null ? "-1" : String(attrs.proto).toLowerCase();
|
|
83
|
+
const proto = raw === "all" || raw === "-1" ? "-1" : raw;
|
|
84
|
+
attrs.proto = proto;
|
|
85
|
+
if (proto === "-1" || attrs.from == null) {
|
|
86
|
+
attrs.from = 0;
|
|
87
|
+
attrs.to = 65535;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
attrs.from = Number(attrs.from);
|
|
91
|
+
attrs.to = attrs.to == null ? attrs.from : Number(attrs.to);
|
|
92
|
+
}
|
|
93
|
+
attrs.cidrs = arr(attrs.cidrs).map(String);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build the view of an IR through the packs given. Nodes of a kind no
|
|
98
|
+
* mapping labels are left out, as are edges no mapping names.
|
|
99
|
+
*/
|
|
100
|
+
export function buildView(ir, packs) {
|
|
101
|
+
const byLexicon = new Map();
|
|
102
|
+
for (const p of packs) {
|
|
103
|
+
const prev = byLexicon.get(p.mapping.lexicon);
|
|
104
|
+
byLexicon.set(p.mapping.lexicon, prev ? { mapping: mergeMappings(prev.mapping, p.mapping), adapter: { ...prev.adapter, ...p.adapter } } : p);
|
|
105
|
+
}
|
|
106
|
+
const terraformPack = [...byLexicon.values()].find((p) => p.mapping.types === "terraform");
|
|
107
|
+
const kindPacks = [...byLexicon.values()].filter((p) => p.mapping.types !== "terraform");
|
|
108
|
+
const nodes = new Map();
|
|
109
|
+
const edges = [];
|
|
110
|
+
const lexicons = new Set();
|
|
111
|
+
const irById = new Map(ir.nodes.map((n) => [n.id, n]));
|
|
112
|
+
const packOf = new Map();
|
|
113
|
+
const typeAndPack = (n) => {
|
|
114
|
+
const tf = terraformType(n);
|
|
115
|
+
if (tf !== undefined)
|
|
116
|
+
return terraformPack ? { type: tf, pack: terraformPack } : undefined;
|
|
117
|
+
const kind = n.kind ?? "";
|
|
118
|
+
const pack = kindPacks.find((p) => p.mapping.labels?.[kind] !== undefined);
|
|
119
|
+
return pack ? { type: kind, pack } : undefined;
|
|
120
|
+
};
|
|
121
|
+
const bodyOf = (n, pack) => {
|
|
122
|
+
const attrs = (n.attrs ?? {});
|
|
123
|
+
if (!pack.mapping.body)
|
|
124
|
+
return attrs;
|
|
125
|
+
const b = attrs[pack.mapping.body];
|
|
126
|
+
return b && typeof b === "object" && !Array.isArray(b) ? b : {};
|
|
127
|
+
};
|
|
128
|
+
// Terraform locals, per scope, so `tags = local.restricted` reads as a map.
|
|
129
|
+
const locals = new Map();
|
|
130
|
+
const scopeOf = (id) => id.slice(0, id.lastIndexOf("/") + 1);
|
|
131
|
+
for (const n of ir.nodes) {
|
|
132
|
+
if (n.kind !== "Terraform::Locals")
|
|
133
|
+
continue;
|
|
134
|
+
const body = (n.attrs?.body ?? {});
|
|
135
|
+
locals.set(scopeOf(n.id), { ...locals.get(scopeOf(n.id)), ...body });
|
|
136
|
+
}
|
|
137
|
+
const resolveLocal = (from, v) => {
|
|
138
|
+
const m = typeof v === "string" ? /^\$\{local\.([\w-]+)\}$/.exec(v) : null;
|
|
139
|
+
return m ? locals.get(scopeOf(from))?.[m[1]] : v;
|
|
140
|
+
};
|
|
141
|
+
// ---- nodes
|
|
142
|
+
for (const n of ir.nodes) {
|
|
143
|
+
const tp = typeAndPack(n);
|
|
144
|
+
if (!tp)
|
|
145
|
+
continue;
|
|
146
|
+
const { type, pack } = tp;
|
|
147
|
+
const m = pack.mapping;
|
|
148
|
+
const spec = m.labels?.[type];
|
|
149
|
+
if (spec === undefined)
|
|
150
|
+
continue;
|
|
151
|
+
const label = typeof spec === "string" ? spec : spec.label;
|
|
152
|
+
const body = bodyOf(n, pack);
|
|
153
|
+
if (typeof spec !== "string" && !matchesWhen(body, spec.when))
|
|
154
|
+
continue;
|
|
155
|
+
const attrs = { type };
|
|
156
|
+
if (typeof spec !== "string" && spec.style)
|
|
157
|
+
attrs.style = spec.style;
|
|
158
|
+
if (typeof spec !== "string" && spec.set)
|
|
159
|
+
Object.assign(attrs, spec.set);
|
|
160
|
+
for (const [neutral, src] of Object.entries(m.attrs?.[label] ?? {})) {
|
|
161
|
+
const v = readSource(body, src);
|
|
162
|
+
if (v !== undefined)
|
|
163
|
+
attrs[neutral] = resolveLocal(n.id, v);
|
|
164
|
+
}
|
|
165
|
+
// Carry the IR's own derived attributes (a live read's `internetFacing`).
|
|
166
|
+
for (const [k, v] of Object.entries((n.attrs ?? {}))) {
|
|
167
|
+
if (k === "internetFacing" && attrs[k] === undefined)
|
|
168
|
+
attrs[k] = v;
|
|
169
|
+
}
|
|
170
|
+
if (m.classificationTags?.length) {
|
|
171
|
+
for (const path of m.tags ?? []) {
|
|
172
|
+
const tags = resolveLocal(n.id, readPath(body, path)[0]);
|
|
173
|
+
const map = Array.isArray(tags)
|
|
174
|
+
? Object.fromEntries(tags.map((t) => [t.Key, t.Value]))
|
|
175
|
+
: tags && typeof tags === "object"
|
|
176
|
+
? tags
|
|
177
|
+
: {};
|
|
178
|
+
for (const key of m.classificationTags) {
|
|
179
|
+
if (map[key] !== undefined && attrs.classification === undefined)
|
|
180
|
+
attrs.classification = map[key];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
normalize(label, attrs);
|
|
185
|
+
nodes.set(n.id, { id: n.id, label, irId: n.id, lexicon: m.lexicon, attrs, body });
|
|
186
|
+
packOf.set(n.id, pack);
|
|
187
|
+
lexicons.add(m.lexicon);
|
|
188
|
+
}
|
|
189
|
+
// ---- references inside attribute values
|
|
190
|
+
const byName = new Map();
|
|
191
|
+
for (const v of nodes.values()) {
|
|
192
|
+
const name = v.attrs.name;
|
|
193
|
+
if (typeof name !== "string" || !name)
|
|
194
|
+
continue;
|
|
195
|
+
if (!byName.has(v.label))
|
|
196
|
+
byName.set(v.label, new Map());
|
|
197
|
+
byName.get(v.label).set(name, v.id);
|
|
198
|
+
}
|
|
199
|
+
const refTarget = (from, value) => {
|
|
200
|
+
const pack = packOf.get(from);
|
|
201
|
+
if (value && typeof value === "object") {
|
|
202
|
+
const ref = value.$ref;
|
|
203
|
+
if (typeof ref === "string") {
|
|
204
|
+
if (nodes.has(ref))
|
|
205
|
+
return { id: ref };
|
|
206
|
+
const dot = ref.lastIndexOf(".");
|
|
207
|
+
if (dot > 0 && nodes.has(ref.slice(0, dot)))
|
|
208
|
+
return { id: ref.slice(0, dot), attr: ref.slice(dot + 1) };
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
const sub = value.$sub;
|
|
212
|
+
if (typeof sub === "string") {
|
|
213
|
+
const m = /\$\{([^.}]+)(?:\.([^}]+))?\}/.exec(sub);
|
|
214
|
+
if (m && nodes.has(m[1]))
|
|
215
|
+
return { id: m[1], attr: m[2] };
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
if (typeof value !== "string")
|
|
220
|
+
return undefined;
|
|
221
|
+
if (pack?.mapping.refs === "terraform") {
|
|
222
|
+
const m = /(?:^|[^\w.])((?:data\.)?[a-z][a-z0-9]*_[a-z0-9_]+\.[A-Za-z0-9_-]+)(?:\[[^\]]*\])?\.([A-Za-z_]+)/.exec(value);
|
|
223
|
+
if (m) {
|
|
224
|
+
const id = scopeOf(from) + m[1];
|
|
225
|
+
if (nodes.has(id))
|
|
226
|
+
return { id, attr: m[2] };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const { label, re } of ARN_NAMES) {
|
|
230
|
+
const m = re.exec(value);
|
|
231
|
+
const id = m ? byName.get(label)?.get(m[1]) : undefined;
|
|
232
|
+
if (id)
|
|
233
|
+
return { id };
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
};
|
|
237
|
+
const unresolved = [];
|
|
238
|
+
// An ARN that names nothing declared here is kept for a workspace's composer (#4378).
|
|
239
|
+
const keepArn = (from, fromAttr, edge, value) => {
|
|
240
|
+
if (typeof value === "string" && ARN_NAMES.some(({ re }) => re.test(value)))
|
|
241
|
+
unresolved.push({ from, fromAttr, edge, value });
|
|
242
|
+
};
|
|
243
|
+
const blockEdges = [];
|
|
244
|
+
// ---- inline blocks: ingress rules, inline routes
|
|
245
|
+
for (const v of [...nodes.values()]) {
|
|
246
|
+
const pack = packOf.get(v.id);
|
|
247
|
+
const specs = pack.mapping.blocks?.[v.label];
|
|
248
|
+
if (!specs)
|
|
249
|
+
continue;
|
|
250
|
+
const body = bodyOf(irById.get(v.id), pack);
|
|
251
|
+
for (const spec of specs) {
|
|
252
|
+
readPath(body, spec.attr)
|
|
253
|
+
.flatMap(arr)
|
|
254
|
+
.forEach((raw, i) => {
|
|
255
|
+
if (!raw || typeof raw !== "object" || !matchesWhen(raw, spec.when))
|
|
256
|
+
return;
|
|
257
|
+
const id = `${v.id}#${spec.attr.replace(/\[\]/g, "")}[${i}]`;
|
|
258
|
+
const attrs = { block: true };
|
|
259
|
+
if (spec.style)
|
|
260
|
+
attrs.style = spec.style;
|
|
261
|
+
for (const [neutral, src] of Object.entries(spec.attrs ?? {})) {
|
|
262
|
+
const val = readSource(raw, src);
|
|
263
|
+
if (val !== undefined)
|
|
264
|
+
attrs[neutral] = val;
|
|
265
|
+
}
|
|
266
|
+
normalize(spec.label, attrs);
|
|
267
|
+
nodes.set(id, { id, label: spec.label, irId: v.id, lexicon: v.lexicon, attrs, body: raw });
|
|
268
|
+
packOf.set(id, pack);
|
|
269
|
+
const back = spec.edge.startsWith("-");
|
|
270
|
+
const name = back ? spec.edge.slice(1) : spec.edge;
|
|
271
|
+
const hop = { from: v.id, fromAttr: spec.attr.replace(/\[\]/g, ""), to: id, edge: name };
|
|
272
|
+
blockEdges.push(back ? { from: id, to: v.id, name, hops: [hop] } : { from: v.id, to: id, name, hops: [hop] });
|
|
273
|
+
for (const [attr, edge] of Object.entries(spec.refs ?? {})) {
|
|
274
|
+
for (const val of arr(raw[attr])) {
|
|
275
|
+
const t = refTarget(v.id, val);
|
|
276
|
+
if (!t)
|
|
277
|
+
keepArn(id, attr, edge, val);
|
|
278
|
+
if (t)
|
|
279
|
+
blockEdges.push({ from: id, to: t.id, name: edge, hops: [{ from: id, fromAttr: attr, to: t.id, ...(t.attr ? { toAttr: t.attr } : {}), edge }] });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Blocks first: an inline route is read before a separate route resource,
|
|
286
|
+
// the order the folds always used.
|
|
287
|
+
edges.push(...blockEdges);
|
|
288
|
+
// ---- IR edges, named per label
|
|
289
|
+
for (const e of ir.edges ?? []) {
|
|
290
|
+
const from = nodes.get(e.from);
|
|
291
|
+
const to = nodes.get(e.to);
|
|
292
|
+
if (!from || !to)
|
|
293
|
+
continue;
|
|
294
|
+
const pack = packOf.get(e.from);
|
|
295
|
+
const via = e.viaAttr ?? e.kind ?? "";
|
|
296
|
+
const spec = pack.mapping.edges?.[from.label]?.[via];
|
|
297
|
+
const name = typeof spec === "string" ? spec : spec?.[to.label];
|
|
298
|
+
if (!name)
|
|
299
|
+
continue;
|
|
300
|
+
const back = name.startsWith("-");
|
|
301
|
+
const n = back ? name.slice(1) : name;
|
|
302
|
+
const hop = irHop(e, n);
|
|
303
|
+
edges.push(back ? { from: e.to, to: e.from, name: n, hops: [hop] } : { from: e.from, to: e.to, name: n, hops: [hop] });
|
|
304
|
+
}
|
|
305
|
+
// ---- link resources -> one edge
|
|
306
|
+
for (const v of [...nodes.values()]) {
|
|
307
|
+
const link = packOf.get(v.id)?.mapping.links?.[v.label];
|
|
308
|
+
if (!link)
|
|
309
|
+
continue;
|
|
310
|
+
const a = edges.find((e) => e.from === v.id && e.name === link.from);
|
|
311
|
+
const b = edges.find((e) => e.from === v.id && e.name === link.to);
|
|
312
|
+
if (a && b)
|
|
313
|
+
edges.push({ from: a.to, to: b.to, name: link.edge, hops: [...a.hops, ...b.hops] });
|
|
314
|
+
}
|
|
315
|
+
// ---- policy documents -> statements
|
|
316
|
+
for (const holder of [...nodes.values()]) {
|
|
317
|
+
if (holder.attrs.block)
|
|
318
|
+
continue;
|
|
319
|
+
const pack = packOf.get(holder.id);
|
|
320
|
+
const docs = pack.mapping.documents?.[holder.label];
|
|
321
|
+
if (!docs)
|
|
322
|
+
continue;
|
|
323
|
+
const body = bodyOf(irById.get(holder.id), pack);
|
|
324
|
+
for (const [path, docKind] of Object.entries(docs)) {
|
|
325
|
+
readPath(body, path).forEach((raw, di) => {
|
|
326
|
+
const doc = readDocument(raw, pack);
|
|
327
|
+
if (!doc)
|
|
328
|
+
return;
|
|
329
|
+
const base = path.replace(/\[\]/g, "");
|
|
330
|
+
arr(doc.Statement).forEach((s0, si) => {
|
|
331
|
+
if (!s0 || typeof s0 !== "object")
|
|
332
|
+
return;
|
|
333
|
+
const s = s0;
|
|
334
|
+
const id = `${holder.id}#${base}[${di}].Statement[${si}]`;
|
|
335
|
+
const principal = s.Principal;
|
|
336
|
+
const principals = principal === "*"
|
|
337
|
+
? ["*"]
|
|
338
|
+
: principal && typeof principal === "object"
|
|
339
|
+
? Object.values(principal).flatMap(arr).map((p) => (typeof p === "string" ? p : "ref"))
|
|
340
|
+
: [];
|
|
341
|
+
const resources = arr(s.Resource);
|
|
342
|
+
const attrs = {
|
|
343
|
+
doc: docKind,
|
|
344
|
+
effect: s.Effect,
|
|
345
|
+
actions: arr(s.Action).map(String),
|
|
346
|
+
...(s.NotAction !== undefined ? { notActions: arr(s.NotAction).map(String) } : {}),
|
|
347
|
+
...(s.NotResource !== undefined ? { notResource: true } : {}),
|
|
348
|
+
...(s.Condition !== undefined ? { condition: true } : {}),
|
|
349
|
+
...(resources.some((r) => r === "*") ? { anyResource: true } : {}),
|
|
350
|
+
principals,
|
|
351
|
+
};
|
|
352
|
+
nodes.set(id, { id, label: "Statement", irId: holder.id, lexicon: holder.lexicon, attrs });
|
|
353
|
+
packOf.set(id, pack);
|
|
354
|
+
edges.push({ from: holder.id, to: id, name: "statement", hops: [{ from: holder.id, fromAttr: base, to: id, edge: "statement" }] });
|
|
355
|
+
let unresolved = 0;
|
|
356
|
+
resources.forEach((r, ri) => {
|
|
357
|
+
const t = refTarget(holder.id, r);
|
|
358
|
+
// A reference the graph holds but can't name (an intrinsic with
|
|
359
|
+
// no target, a Terraform expression to an undeclared resource) is
|
|
360
|
+
// counted, so an answer can say what it skipped. A `Sub` is read
|
|
361
|
+
// through its `$sub` template (chant#4373).
|
|
362
|
+
if (!t && ((r && typeof r === "object") || (typeof r === "string" && r.includes("${"))))
|
|
363
|
+
unresolved++;
|
|
364
|
+
if (!t)
|
|
365
|
+
keepArn(id, `Resource[${ri}]`, "resource", r);
|
|
366
|
+
if (t)
|
|
367
|
+
edges.push({ from: id, to: t.id, name: "resource", hops: [{ from: id, fromAttr: `Resource[${ri}]`, to: t.id, ...(t.attr ? { toAttr: t.attr } : {}), edge: "resource" }] });
|
|
368
|
+
});
|
|
369
|
+
if (unresolved > 0)
|
|
370
|
+
attrs.unresolved = unresolved;
|
|
371
|
+
if (principal && typeof principal === "object") {
|
|
372
|
+
for (const p of arr(principal.AWS)) {
|
|
373
|
+
const t = refTarget(holder.id, p);
|
|
374
|
+
if (!t)
|
|
375
|
+
keepArn(id, "Principal.AWS", "principal", p);
|
|
376
|
+
if (t)
|
|
377
|
+
edges.push({ from: id, to: t.id, name: "principal", hops: [{ from: id, fromAttr: "Principal.AWS", to: t.id, ...(t.attr ? { toAttr: t.attr } : {}), edge: "principal" }] });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return { nodes, edges, lexicons, unresolved };
|
|
385
|
+
}
|
|
386
|
+
/** The version of the serialized view a member's `chant graph --capability-view` prints (#4378). */
|
|
387
|
+
export const VIEW_DOCUMENT_VERSION = 1;
|
|
388
|
+
export function serializeView(view) {
|
|
389
|
+
return {
|
|
390
|
+
version: VIEW_DOCUMENT_VERSION,
|
|
391
|
+
lexicons: [...view.lexicons].sort(),
|
|
392
|
+
nodes: [...view.nodes.values()].map((n) => ({ id: n.id, label: n.label, irId: n.irId, lexicon: n.lexicon, attrs: n.attrs })),
|
|
393
|
+
edges: view.edges,
|
|
394
|
+
unresolved: view.unresolved ?? [],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** Whether a member printed a view this chant reads; anything else is treated as no view. */
|
|
398
|
+
export function isViewDocument(v) {
|
|
399
|
+
if (!v || typeof v !== "object")
|
|
400
|
+
return false;
|
|
401
|
+
const d = v;
|
|
402
|
+
return d.version === VIEW_DOCUMENT_VERSION && Array.isArray(d.nodes) && Array.isArray(d.edges) && Array.isArray(d.lexicons);
|
|
403
|
+
}
|
|
404
|
+
/** A view document with every id it holds given `prefix` (`<member>/`), as `workspace graph` composes ids. */
|
|
405
|
+
export function prefixView(doc, prefix) {
|
|
406
|
+
const p = (id) => `${prefix}${id}`;
|
|
407
|
+
const hop = (h) => ({ ...h, from: p(h.from), to: p(h.to) });
|
|
408
|
+
return {
|
|
409
|
+
version: doc.version,
|
|
410
|
+
lexicons: doc.lexicons,
|
|
411
|
+
nodes: doc.nodes.map((n) => ({ ...n, id: p(n.id), irId: p(n.irId) })),
|
|
412
|
+
edges: doc.edges.map((e) => ({ ...e, from: p(e.from), to: p(e.to), hops: e.hops.map(hop) })),
|
|
413
|
+
unresolved: (doc.unresolved ?? []).map((u) => ({ ...u, from: p(u.from) })),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Compose member views into one (#4378), the matcher's input over a
|
|
418
|
+
* workspace. Each member's view was mapped by its own chant; the one thing
|
|
419
|
+
* left to do here is resolve the ARNs a member couldn't, by name over every
|
|
420
|
+
* member's nodes. Where several members declare the name, the last one in
|
|
421
|
+
* member order wins, as within one view.
|
|
422
|
+
*/
|
|
423
|
+
export function composeViews(docs) {
|
|
424
|
+
const nodes = new Map();
|
|
425
|
+
const edges = [];
|
|
426
|
+
const lexicons = new Set();
|
|
427
|
+
for (const d of docs) {
|
|
428
|
+
for (const n of d.nodes)
|
|
429
|
+
nodes.set(n.id, { ...n });
|
|
430
|
+
edges.push(...d.edges);
|
|
431
|
+
for (const l of d.lexicons)
|
|
432
|
+
lexicons.add(l);
|
|
433
|
+
}
|
|
434
|
+
const byName = new Map();
|
|
435
|
+
for (const v of nodes.values()) {
|
|
436
|
+
const name = v.attrs.name;
|
|
437
|
+
if (typeof name !== "string" || !name)
|
|
438
|
+
continue;
|
|
439
|
+
if (!byName.has(v.label))
|
|
440
|
+
byName.set(v.label, new Map());
|
|
441
|
+
byName.get(v.label).set(name, v.id);
|
|
442
|
+
}
|
|
443
|
+
const unresolved = [];
|
|
444
|
+
for (const u of docs.flatMap((d) => d.unresolved ?? [])) {
|
|
445
|
+
let to;
|
|
446
|
+
for (const { label, re } of ARN_NAMES) {
|
|
447
|
+
const m = re.exec(u.value);
|
|
448
|
+
to = m ? byName.get(label)?.get(m[1]) : undefined;
|
|
449
|
+
if (to)
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
if (to)
|
|
453
|
+
edges.push({ from: u.from, to, name: u.edge, hops: [{ from: u.from, fromAttr: u.fromAttr, to, edge: u.edge }] });
|
|
454
|
+
else
|
|
455
|
+
unresolved.push(u);
|
|
456
|
+
}
|
|
457
|
+
return { nodes, edges, lexicons, unresolved };
|
|
458
|
+
}
|
|
459
|
+
function irHop(e, name) {
|
|
460
|
+
return {
|
|
461
|
+
from: e.from,
|
|
462
|
+
...(e.viaAttr ? { fromAttr: e.viaAttr } : {}),
|
|
463
|
+
to: e.to,
|
|
464
|
+
...(e.toAttr ? { toAttr: e.toAttr } : {}),
|
|
465
|
+
edge: name,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function readDocument(raw, pack) {
|
|
469
|
+
if (pack.adapter?.readDocument)
|
|
470
|
+
return pack.adapter.readDocument(raw);
|
|
471
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
472
|
+
const inner = raw.PolicyDocument;
|
|
473
|
+
return inner && typeof inner === "object" ? inner : raw;
|
|
474
|
+
}
|
|
475
|
+
if (typeof raw === "string") {
|
|
476
|
+
try {
|
|
477
|
+
return JSON.parse(raw);
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|