@intentius/chant 0.124.45 → 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/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +1 -0
- 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.map +1 -1
- package/dist/cli/handlers/pr-env.js +2 -1
- 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/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 +5 -21
- package/dist/components/pr-env-run.d.ts.map +1 -1
- package/dist/components/pr-env-run.js +6 -70
- package/dist/components/pr-environment.d.ts +2 -1
- package/dist/components/pr-environment.d.ts.map +1 -1
- package/dist/components/pr-environment.js +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/wave-runtime.mjs +250 -249
- 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/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/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/cli/commands/lint.ts +1 -0
- 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 +1 -2
- 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/pr-env-ports.ts +89 -0
- package/src/components/pr-env-run-bundle.test.ts +54 -0
- package/src/components/pr-env-run.ts +5 -76
- package/src/components/pr-environment.ts +2 -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/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/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/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
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/lint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAW,MAAM,iBAAiB,CAAC;AAyCzE;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAW5D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAoBhC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,WAAW,CAAC;AAsBnD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,WAAW,CAAC;AA2I9C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACrC,uBAAuB;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oBAAoB;IACpB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,qCAAqC;IACrC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/lint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAW,MAAM,iBAAiB,CAAC;AAyCzE;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAW5D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAoBhC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,WAAW,CAAC;AAsBnD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,WAAW,CAAC;AA2I9C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACrC,uBAAuB;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oBAAoB;IACpB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,qCAAqC;IACrC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAgfD;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA+N3E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAIxD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GACtC,MAAM,IAAI,CAiCZ"}
|
|
@@ -653,6 +653,7 @@ async function runLexiconPostSynthDiagnostics(infraPath, plugins, buildParams, s
|
|
|
653
653
|
ruleId: d.checkId,
|
|
654
654
|
severity: d.severity,
|
|
655
655
|
message: `${d.entity ? `[${d.entity}] ` : ""}${d.message}`,
|
|
656
|
+
...(d.entity ? { entity: d.entity } : {}),
|
|
656
657
|
});
|
|
657
658
|
for (const { plugin, diagnostics: raw } of runLexiconPostSynthChecks(withChecks, result, env, { activityContracts, telemetry, projectDir: configDir, ...(loadedConfig ? { projectConfig: loadedConfig } : {}) })) {
|
|
658
659
|
const presetIds = resolvePresetIds(plugin.lintPresets?.(), lintConfig.presets?.[plugin.name] ?? "recommended");
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Declarable } from "../../declarable.js";
|
|
2
|
+
import { type GraphIR } from "../../graph-ir.js";
|
|
3
|
+
import type { BuildRootContributor, LexiconPlugin } from "../../lexicon.js";
|
|
4
|
+
import type { CommandContext } from "../registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* The pieces of the declared graph that `chant graph` and `chant search` both
|
|
7
|
+
* read (#1626, #4365, #4368). They live here, once, so the two commands answer
|
|
8
|
+
* over the same entity set and the same edges: search drifted from graph once
|
|
9
|
+
* already, answering `(no matches)` over a `terraform.roots` project graph drew
|
|
10
|
+
* in full, because it never ran the build-root contributors.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The build-root contributors this invocation's project configures (#1626):
|
|
14
|
+
* a `terraform.roots` root's blocks, a `k8s.kustomize.roots` root's rendered
|
|
15
|
+
* objects. The same closures `chant build` and the lifecycle handlers hand to
|
|
16
|
+
* `BuildOptions.buildRoots`, bound here for the discover-based paths, which
|
|
17
|
+
* never run `build()` and so never ran the hook.
|
|
18
|
+
*
|
|
19
|
+
* Neither command is `requiresPlugins` (Op/source-graph modes must work
|
|
20
|
+
* without a lexicon), so plugins are loaded here when `ctx.plugins` is empty.
|
|
21
|
+
* Rooted at the config's own directory, like `cli/commands/build.ts`: a
|
|
22
|
+
* relative root resolves against where it was declared, not against a
|
|
23
|
+
* sourceDir-scoped path. A project that fails to load plugins degrades to no
|
|
24
|
+
* contributors with a warning rather than failing modes that never needed
|
|
25
|
+
* plugins at all.
|
|
26
|
+
*/
|
|
27
|
+
export declare function declaredBuildRootContributors(ctx: CommandContext, projectPath: string): Promise<BuildRootContributor[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Merge build-root contributions into a discovered entity set: the exact
|
|
30
|
+
* merge `build()` performs (one implementation: `mergeBuildRootEntities`,
|
|
31
|
+
* ../../build.ts), with its warnings and errors printed. Returns false when a
|
|
32
|
+
* contributor failed or a name collided; the caller stops, because a graph
|
|
33
|
+
* missing the entities the build would refuse over must not be answered from
|
|
34
|
+
* as if complete.
|
|
35
|
+
*/
|
|
36
|
+
export declare function mergeDeclaredBuildRoots(entities: Map<string, Declarable>, contributors: BuildRootContributor[]): Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Add the edges the lexicons' `graphEdges` hooks resolve (#3910): CI
|
|
39
|
+
* dependencies written as strings (`needs`, `uses:`, `trigger:`, `include:`,
|
|
40
|
+
* `extends`), which the AttrRef walk in `buildGraphIr` cannot see. A hook that
|
|
41
|
+
* throws costs its own edges and a warning, not the graph.
|
|
42
|
+
*/
|
|
43
|
+
export declare function withLexiconEdges(ir: GraphIR, plugins: readonly LexiconPlugin[], entities: Map<string, Declarable>): GraphIR;
|
|
44
|
+
//# sourceMappingURL=declared-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declared-graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/declared-graph.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAA0B,KAAK,OAAO,EAA0B,MAAM,gBAAgB,CAAC;AAC9F,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,cAAc,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAoBjC;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,YAAY,EAAE,oBAAoB,EAAE,GACnC,OAAO,CAAC,OAAO,CAAC,CASlB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,OAAO,CAY3H"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { mergeBuildRootEntities } from "../../build.js";
|
|
3
|
+
import { loadChantConfigUpward } from "../../config.js";
|
|
4
|
+
import { applyLexiconGraphEdges } from "../../graph-ir.js";
|
|
5
|
+
import { formatError, formatWarning } from "../format.js";
|
|
6
|
+
import { collectBuildRootContributors, loadPlugins, resolveProjectLexicons } from "../plugins.js";
|
|
7
|
+
/**
|
|
8
|
+
* The pieces of the declared graph that `chant graph` and `chant search` both
|
|
9
|
+
* read (#1626, #4365, #4368). They live here, once, so the two commands answer
|
|
10
|
+
* over the same entity set and the same edges: search drifted from graph once
|
|
11
|
+
* already, answering `(no matches)` over a `terraform.roots` project graph drew
|
|
12
|
+
* in full, because it never ran the build-root contributors.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The build-root contributors this invocation's project configures (#1626):
|
|
16
|
+
* a `terraform.roots` root's blocks, a `k8s.kustomize.roots` root's rendered
|
|
17
|
+
* objects. The same closures `chant build` and the lifecycle handlers hand to
|
|
18
|
+
* `BuildOptions.buildRoots`, bound here for the discover-based paths, which
|
|
19
|
+
* never run `build()` and so never ran the hook.
|
|
20
|
+
*
|
|
21
|
+
* Neither command is `requiresPlugins` (Op/source-graph modes must work
|
|
22
|
+
* without a lexicon), so plugins are loaded here when `ctx.plugins` is empty.
|
|
23
|
+
* Rooted at the config's own directory, like `cli/commands/build.ts`: a
|
|
24
|
+
* relative root resolves against where it was declared, not against a
|
|
25
|
+
* sourceDir-scoped path. A project that fails to load plugins degrades to no
|
|
26
|
+
* contributors with a warning rather than failing modes that never needed
|
|
27
|
+
* plugins at all.
|
|
28
|
+
*/
|
|
29
|
+
export async function declaredBuildRootContributors(ctx, projectPath) {
|
|
30
|
+
const { config, configPath } = await loadChantConfigUpward(projectPath).catch(() => ({ config: {}, configPath: undefined }));
|
|
31
|
+
let plugins = ctx.plugins;
|
|
32
|
+
if (plugins.length === 0) {
|
|
33
|
+
try {
|
|
34
|
+
plugins = await loadPlugins(await resolveProjectLexicons(projectPath));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error(formatWarning({
|
|
38
|
+
message: `could not load the project's lexicons — build-root entities (terraform and kustomize roots) will be missing from the graph (${err instanceof Error ? err.message : String(err)})`,
|
|
39
|
+
}));
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return collectBuildRootContributors(plugins, config, configPath ? dirname(configPath) : projectPath);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Merge build-root contributions into a discovered entity set: the exact
|
|
47
|
+
* merge `build()` performs (one implementation: `mergeBuildRootEntities`,
|
|
48
|
+
* ../../build.ts), with its warnings and errors printed. Returns false when a
|
|
49
|
+
* contributor failed or a name collided; the caller stops, because a graph
|
|
50
|
+
* missing the entities the build would refuse over must not be answered from
|
|
51
|
+
* as if complete.
|
|
52
|
+
*/
|
|
53
|
+
export async function mergeDeclaredBuildRoots(entities, contributors) {
|
|
54
|
+
if (contributors.length === 0)
|
|
55
|
+
return true;
|
|
56
|
+
const merged = await mergeBuildRootEntities(entities, contributors);
|
|
57
|
+
for (const w of merged.warnings)
|
|
58
|
+
console.error(formatWarning({ message: w }));
|
|
59
|
+
if (merged.errors.length > 0) {
|
|
60
|
+
for (const message of merged.errors)
|
|
61
|
+
console.error(formatError({ message }));
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Add the edges the lexicons' `graphEdges` hooks resolve (#3910): CI
|
|
68
|
+
* dependencies written as strings (`needs`, `uses:`, `trigger:`, `include:`,
|
|
69
|
+
* `extends`), which the AttrRef walk in `buildGraphIr` cannot see. A hook that
|
|
70
|
+
* throws costs its own edges and a warning, not the graph.
|
|
71
|
+
*/
|
|
72
|
+
export function withLexiconEdges(ir, plugins, entities) {
|
|
73
|
+
const reports = [];
|
|
74
|
+
for (const p of plugins) {
|
|
75
|
+
if (!p.graphEdges)
|
|
76
|
+
continue;
|
|
77
|
+
try {
|
|
78
|
+
const report = p.graphEdges(entities);
|
|
79
|
+
if (report)
|
|
80
|
+
reports.push(report);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error(formatWarning({ message: `the ${p.name} lexicon's graphEdges failed (${err instanceof Error ? err.message : String(err)}); its edges are left out` }));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return reports.length === 0 ? ir : applyLexiconGraphEdges(ir, reports);
|
|
87
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LintResult } from "../commands/lint.js";
|
|
1
2
|
import type { CommandContext } from "../registry.js";
|
|
2
3
|
/**
|
|
3
4
|
* `chant graph` — the Op dependency graph by default; `--stacks` renders the
|
|
@@ -16,4 +17,28 @@ import type { CommandContext } from "../registry.js";
|
|
|
16
17
|
* is the read the other gets.
|
|
17
18
|
*/
|
|
18
19
|
export declare function runGraph(ctx: CommandContext): Promise<number>;
|
|
20
|
+
/** One lint finding as `meta.lint` carries it (INTENTIUS/behold#919). */
|
|
21
|
+
export interface GraphLintFinding {
|
|
22
|
+
ruleId: string;
|
|
23
|
+
severity: string;
|
|
24
|
+
message: string;
|
|
25
|
+
/** Relative to the project when inside it, else as lint reported it. */
|
|
26
|
+
file: string;
|
|
27
|
+
line: number;
|
|
28
|
+
column: number;
|
|
29
|
+
/** The entity a post-synth check named, which is the graph node's id. Absent for a source-file rule. */
|
|
30
|
+
entity?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* What a `graph.lint: "report"` project's entity graph carries on `meta.lint`:
|
|
34
|
+
* the counts `chant lint` would print and every finding behind them, warnings
|
|
35
|
+
* included. Present on every graph such a project emits, findings or none, so
|
|
36
|
+
* a reader can tell "linted, clean" from "not looked" (no key).
|
|
37
|
+
*/
|
|
38
|
+
export interface GraphLintReport {
|
|
39
|
+
errors: number;
|
|
40
|
+
warnings: number;
|
|
41
|
+
findings: GraphLintFinding[];
|
|
42
|
+
}
|
|
43
|
+
export declare function graphLintReport(lint: LintResult, projectPath: string): GraphLintReport;
|
|
19
44
|
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/graph.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/graph.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAYhE,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,aAAa,CAAC;AAgE9D;;;;;;;;;GASG;AACH;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAE7D;AAoaD,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,wGAAwG;IACxG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAQD,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,eAAe,CAoBtF"}
|