@intentius/chant-lexicon-k8s 0.98.0 → 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,18 @@
7
7
 
8
8
  import type { TypeScriptGenerator, GeneratedFile } from "@intentius/chant/import/generator";
9
9
  import type { TemplateIR, ResourceIR } from "@intentius/chant/import/parser";
10
+ import {
11
+ EmbeddedImportScope,
12
+ isEmbeddedReference,
13
+ renderEmbeddedReference,
14
+ } from "@intentius/chant/import/embedded";
15
+
16
+ const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
17
+
18
+ /** An object key as TypeScript: bare when it is an identifier, quoted otherwise (`"config.yaml"`, `"app.kubernetes.io/name"`). */
19
+ function propertyKey(key: string): string {
20
+ return IDENT.test(key) ? key : JSON.stringify(key);
21
+ }
10
22
 
11
23
  /**
12
24
  * Map K8s entity types to their constructor class names.
@@ -99,6 +111,9 @@ const SERVICE_PORT_TYPES = new Set([
99
111
  * Generate TypeScript source code from a Kubernetes IR.
100
112
  */
101
113
  export class K8sGenerator implements TypeScriptGenerator {
114
+ /** The imports for embedded content the current `generate()` call references (#2962). */
115
+ private scope = new EmbeddedImportScope("");
116
+
102
117
  generate(ir: TemplateIR): GeneratedFile[] {
103
118
  const lines: string[] = [];
104
119
 
@@ -112,10 +127,13 @@ export class K8sGenerator implements TypeScriptGenerator {
112
127
  this.collectNestedConstructors(resource.properties, usedConstructors, resource.type);
113
128
  }
114
129
 
115
- // Import statement
116
- const imports = [...usedConstructors].sort().join(", ");
117
- lines.push(`import { ${imports} } from "@intentius/chant-lexicon-k8s";`);
118
- lines.push("");
130
+ // Names the embedded content's imports must not take: every constructor this file may use, and its exports.
131
+ this.scope = new EmbeddedImportScope("", [
132
+ ...usedConstructors,
133
+ ...Object.values(PROPERTY_CONSTRUCTORS),
134
+ "ServicePort",
135
+ ...ir.resources.map((r) => r.logicalId),
136
+ ]);
119
137
 
120
138
  // Emit namespaces as comments
121
139
  if (ir.metadata?.namespaces && Array.isArray(ir.metadata.namespaces)) {
@@ -139,7 +157,16 @@ export class K8sGenerator implements TypeScriptGenerator {
139
157
  lines.push("");
140
158
  }
141
159
 
142
- return [{ path: "main.ts", content: lines.join("\n") }];
160
+ // Import statements: the k8s constructors, then what embedded content references.
161
+ // The constructors are read back from what was emitted (string literals
162
+ // taken out), so a nested one the walk above does not reach (a
163
+ // container's env) is imported too.
164
+ const code = lines.join("\n").replace(/"(?:[^"\\\n]|\\.)*"/g, '""');
165
+ for (const m of code.matchAll(/\bnew ([A-Z][A-Za-z0-9_]*)\(/g)) usedConstructors.add(m[1]);
166
+ const imports = [...usedConstructors].sort().join(", ");
167
+ const head = [`import { ${imports} } from "@intentius/chant-lexicon-k8s";`, ...this.scope.lines(), ""];
168
+
169
+ return [{ path: "main.ts", content: [...head, ...lines].join("\n") }];
143
170
  }
144
171
 
145
172
  /**
@@ -199,7 +226,7 @@ export class K8sGenerator implements TypeScriptGenerator {
199
226
  for (const [key, value] of Object.entries(props)) {
200
227
  if (value === undefined || value === null) continue;
201
228
  const emitted = this.emitValue(key, value, depth + 1, parentType);
202
- entries.push(`${innerIndent}${key}: ${emitted},`);
229
+ entries.push(`${innerIndent}${propertyKey(key)}: ${emitted},`);
203
230
  }
204
231
 
205
232
  if (entries.length === 0) return "{}";
@@ -208,6 +235,7 @@ export class K8sGenerator implements TypeScriptGenerator {
208
235
 
209
236
  private emitValue(key: string, value: unknown, depth: number, parentType: string): string {
210
237
  if (value === null || value === undefined) return "undefined";
238
+ if (isEmbeddedReference(value)) return renderEmbeddedReference(value, this.scope, depth * 2);
211
239
 
212
240
  // Special case: "ports" on Service → ServicePort constructor
213
241
  if (key === "ports" && SERVICE_PORT_TYPES.has(parentType) && Array.isArray(value)) {
@@ -256,6 +284,7 @@ export class K8sGenerator implements TypeScriptGenerator {
256
284
 
257
285
  private emitLiteral(value: unknown, depth: number, parentType: string): string {
258
286
  if (value === null || value === undefined) return "undefined";
287
+ if (isEmbeddedReference(value)) return renderEmbeddedReference(value, this.scope, depth * 2);
259
288
  if (typeof value === "string") return JSON.stringify(value);
260
289
  if (typeof value === "number" || typeof value === "boolean") return String(value);
261
290
 
@@ -275,7 +304,7 @@ export class K8sGenerator implements TypeScriptGenerator {
275
304
  const indent = " ".repeat(depth);
276
305
  const innerIndent = " ".repeat(depth + 1);
277
306
  const items = entries.map(
278
- ([k, v]) => `${innerIndent}${k}: ${this.emitLiteral(v, depth + 1, parentType)},`,
307
+ ([k, v]) => `${innerIndent}${propertyKey(k)}: ${this.emitLiteral(v, depth + 1, parentType)},`,
279
308
  );
280
309
  return `{\n${items.join("\n")}\n${indent}}`;
281
310
  }
@@ -6,9 +6,11 @@
6
6
  * convention.
7
7
  */
8
8
 
9
- import type { TemplateParser, TemplateIR, ResourceIR } from "@intentius/chant/import/parser";
9
+ import type { TemplateParser, TemplateIR, ResourceIR, ParseContext } from "@intentius/chant/import/parser";
10
+ import type { EmbeddedContentResolver } from "@intentius/chant/import/embedded";
10
11
  import { parseYAML, splitYAMLDocuments } from "@intentius/chant/yaml";
11
12
  import { namespaceSegmentForGroup } from "../group-namespace";
13
+ import { delegateEmbedded } from "./embedded";
12
14
 
13
15
  // ── GVK to type name mapping ───────────────────────────────────────
14
16
 
@@ -134,7 +136,13 @@ function toLogicalId(kind: string, name: string | undefined): string {
134
136
  * to be a standard Kubernetes object with apiVersion, kind, and metadata.
135
137
  */
136
138
  export class K8sParser implements TemplateParser {
137
- parse(content: string): TemplateIR {
139
+ /**
140
+ * With `context.embedded` (`chant import`), content another lexicon owns
141
+ * is offered to it: every ConfigMap `data` value and a PrometheusRule's
142
+ * `spec.groups` (see ./embedded.ts). Without it, everything is kept as
143
+ * written.
144
+ */
145
+ parse(content: string, context?: ParseContext): TemplateIR {
138
146
  const resources: ResourceIR[] = [];
139
147
  const namespaces = new Set<string>();
140
148
 
@@ -146,7 +154,7 @@ export class K8sParser implements TemplateParser {
146
154
  const doc = parseYAML(docStr);
147
155
  if (!doc || typeof doc !== "object") continue;
148
156
 
149
- const resource = this.parseDocument(doc as Record<string, unknown>);
157
+ const resource = this.parseDocument(doc as Record<string, unknown>, context?.embedded);
150
158
  if (resource) {
151
159
  resources.push(resource);
152
160
 
@@ -170,7 +178,7 @@ export class K8sParser implements TemplateParser {
170
178
  };
171
179
  }
172
180
 
173
- private parseDocument(doc: Record<string, unknown>): ResourceIR | null {
181
+ private parseDocument(doc: Record<string, unknown>, embedded?: EmbeddedContentResolver): ResourceIR | null {
174
182
  const apiVersion = doc.apiVersion as string | undefined;
175
183
  const kind = doc.kind as string | undefined;
176
184
 
@@ -188,6 +196,8 @@ export class K8sParser implements TemplateParser {
188
196
  properties[key] = value;
189
197
  }
190
198
 
199
+ if (embedded) delegateEmbedded(type, kind, properties, embedded);
200
+
191
201
  return {
192
202
  logicalId,
193
203
  type,
@@ -0,0 +1,11 @@
1
+ # Embedded-content import fixtures
2
+
3
+ Manifests that hold another lexicon's content, for `embedded-roundtrip.test.ts`
4
+ and `embedded-types.e2e.test.ts` (#2962).
5
+
6
+ | File | Source |
7
+ |---|---|
8
+ | `otel-collector-daemonset.yaml` | open-telemetry/opentelemetry-helm-charts at `b0bce1305083b574c63d6d537431f0b08d36e17f`, chart opentelemetry-collector 0.173.1 (collector 0.160.0): `charts/opentelemetry-collector/examples/daemonset-only/rendered/configmap-agent.yaml` followed by `daemonset.yaml` from the same directory, unchanged. The config is under the chart's `relay` key. |
9
+ | `node-exporter-prometheusrule.yaml` | prometheus-operator/kube-prometheus `v0.19.0`, `manifests/nodeExporter-prometheusRule.yaml`, unchanged. |
10
+ | `grafana-dashboard-configmap.yaml` | prometheus-operator/kube-prometheus `v0.19.0`, `manifests/grafana-dashboardDefinitions.yaml`: the `grafana-dashboard-alertmanager-overview` item of that ConfigMapList, written as a ConfigMap of its own with the Grafana sidecar's `grafana_dashboard: "1"` label added, re-serialized by js-yaml. The dashboard JSON is unchanged. |
11
+ | `agent-observability.yaml` | `chant build src --lexicon k8s` of `examples/agent-observability` as of #2954: collector ConfigMaps from the k8s collector composites, Prometheus's `rules.yml` from `ruleFileYaml`, and the Grafana dashboard, datasource and provider ConfigMaps from `GrafanaConfigMaps` (`@intentius/chant-lexicon-grafana/k8s`). |