@intentius/chant-lexicon-k8s 0.109.0 → 0.111.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.
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/import/parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAc,YAAY,EAAE,MAAM,gCAAgC,CAAC;AA2H3G;;;;;GAKG;AACH,qBAAa,SAAU,YAAW,cAAc;IAC9C;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAkCzD;IAED,OAAO,CAAC,aAAa;CAgCtB"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/import/parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAc,YAAY,EAAE,MAAM,gCAAgC,CAAC;AA2I3G;;;;;GAKG;AACH,qBAAa,SAAU,YAAW,cAAc;IAC9C;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAkCzD;IAED,OAAO,CAAC,aAAa;CAiCtB"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "4011502a4c4482f6136dfbd519c5fc45a4a468b9fbf08cea8df2a37f833964e9",
4
+ "manifest.json": "9c80ddcd8b7780b98224d896b20f42c0af2d7752de404a6b0e692735d1cd14a1",
5
5
  "meta.json": "c1831ac15083f5e0e45b29698d7b77f1c01646240fe6de5d6320753188594f69",
6
6
  "types/index.d.ts": "5c5340046d090ae9db5553039d923cf92a8b2eb57aa4dc941273ed5bcfb5bc9d",
7
7
  "rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
@@ -78,5 +78,5 @@
78
78
  "skills/chant-k8s-argo.md": "24c9a85b78a2fc5c6657424baac9cc6d6caff6f47605b8146b2c0082133181cf",
79
79
  "skills/chant-k8s-flux.md": "9670efc2e9854bd14686b5adeb707396b9030a4dcaee6529b30abf56efe2c50c"
80
80
  },
81
- "composite": "86e54eabeb0deb4d47207b029ab9d49364773a81c8abfb2cdabefd04b80ca04d"
81
+ "composite": "e90b3b5dd9dadede9d876b773e442554859ae799232033bf8099f70cd48b2968"
82
82
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k8s",
3
- "version": "0.109.0",
3
+ "version": "0.111.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "K8s",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.109.0",
3
+ "version": "0.111.0",
4
4
  "description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -78,7 +78,7 @@
78
78
  "@types/js-yaml": "^4.0.9"
79
79
  },
80
80
  "optionalDependencies": {
81
- "@intentius/chant-k8s-client": "^0.109.0"
81
+ "@intentius/chant-k8s-client": "^0.111.0"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@intentius/chant": "*",
@@ -86,7 +86,7 @@
86
86
  },
87
87
  "peerDependencies": {
88
88
  "zod": "^4.3.6",
89
- "@intentius/chant": "^0.109.0",
89
+ "@intentius/chant": "^0.111.0",
90
90
  "typescript": "^5.9.3"
91
91
  }
92
92
  }
@@ -77,6 +77,22 @@ const GVK_TYPE_MAP: Record<string, string> = {
77
77
  * Resolve apiVersion + kind to a K8s type name.
78
78
  * Falls back to constructing from the apiVersion group.
79
79
  */
80
+ /**
81
+ * `<apiVersion>/<kind>` keys that map to a type another key maps to first.
82
+ * The build writes the first key's apiVersion for that type, so a document
83
+ * written against a later key (`autoscaling/v1` HorizontalPodAutoscaler)
84
+ * keeps its own apiVersion as a property.
85
+ */
86
+ const ALIASED_GVKS: ReadonlySet<string> = (() => {
87
+ const firstForType = new Set<string>();
88
+ const aliased = new Set<string>();
89
+ for (const [key, type] of Object.entries(GVK_TYPE_MAP)) {
90
+ if (firstForType.has(type)) aliased.add(key);
91
+ else firstForType.add(type);
92
+ }
93
+ return aliased;
94
+ })();
95
+
80
96
  function resolveTypeName(apiVersion: string, kind: string): string {
81
97
  // Try exact match
82
98
  const key = `${apiVersion}/${kind}`;
@@ -192,7 +208,8 @@ export class K8sParser implements TemplateParser {
192
208
  // Extract the user-configurable properties (skip apiVersion, kind)
193
209
  const properties: Record<string, unknown> = {};
194
210
  for (const [key, value] of Object.entries(doc)) {
195
- if (key === "apiVersion" || key === "kind") continue;
211
+ if (key === "kind") continue;
212
+ if (key === "apiVersion" && !ALIASED_GVKS.has(`${apiVersion}/${kind}`)) continue;
196
213
  properties[key] = value;
197
214
  }
198
215
 
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Import a manifest, build the generated source, and compare the documents.
3
+ * Each case is a manifest from the Kubernetes documentation examples that did
4
+ * not come back.
5
+ */
6
+ import { describe, expect, test } from "vitest";
7
+ import { loadAll } from "js-yaml";
8
+ import { build } from "@intentius/chant/build";
9
+ import type { SerializerResult } from "@intentius/chant/serializer";
10
+ import { k8sSerializer } from "../serializer";
11
+ import { importManifest, removeDir } from "./testdata/embedded/fixtures";
12
+
13
+ function primary(out: string | SerializerResult | undefined): string {
14
+ if (out === undefined) return "";
15
+ return typeof out === "string" ? out : out.primary;
16
+ }
17
+
18
+ async function roundTrip(yaml: string): Promise<unknown[]> {
19
+ const imported = await importManifest(yaml);
20
+ try {
21
+ expect(imported.result.error).toBeUndefined();
22
+ const result = await build(imported.srcDir, [k8sSerializer]);
23
+ expect(result.errors).toEqual([]);
24
+ return (loadAll(primary(result.outputs.get("k8s"))) as unknown[]).filter((d) => d);
25
+ } finally {
26
+ removeDir(imported.dir);
27
+ }
28
+ }
29
+
30
+ describe("k8s import round trip", () => {
31
+ test("a NetworkPolicy's egress: [{}] comes back as one empty rule", async () => {
32
+ const yaml = [
33
+ "apiVersion: networking.k8s.io/v1",
34
+ "kind: NetworkPolicy",
35
+ "metadata:",
36
+ " name: allow-all-egress",
37
+ "spec:",
38
+ " podSelector: {}",
39
+ " egress:",
40
+ " - {}",
41
+ " policyTypes:",
42
+ " - Egress",
43
+ ].join("\n");
44
+ expect(await roundTrip(yaml)).toEqual(loadAll(yaml));
45
+ });
46
+
47
+ test("an autoscaling/v1 HorizontalPodAutoscaler keeps its apiVersion", async () => {
48
+ const yaml = [
49
+ "apiVersion: autoscaling/v1",
50
+ "kind: HorizontalPodAutoscaler",
51
+ "metadata:",
52
+ " name: frontend-scaler",
53
+ "spec:",
54
+ " scaleTargetRef:",
55
+ " kind: ReplicaSet",
56
+ " name: frontend",
57
+ " minReplicas: 3",
58
+ " maxReplicas: 10",
59
+ " targetCPUUtilizationPercentage: 50",
60
+ ].join("\n");
61
+ expect(await roundTrip(yaml)).toEqual(loadAll(yaml));
62
+ });
63
+
64
+ test("an autoscaling/v2 HorizontalPodAutoscaler is generated without an apiVersion property", async () => {
65
+ const yaml = [
66
+ "apiVersion: autoscaling/v2",
67
+ "kind: HorizontalPodAutoscaler",
68
+ "metadata:",
69
+ " name: web",
70
+ "spec:",
71
+ " scaleTargetRef:",
72
+ " apiVersion: apps/v1",
73
+ " kind: Deployment",
74
+ " name: web",
75
+ " minReplicas: 1",
76
+ " maxReplicas: 3",
77
+ ].join("\n");
78
+ const imported = await importManifest(yaml);
79
+ try {
80
+ const source = Object.values(imported.files).join("\n");
81
+ expect(source).not.toContain('apiVersion: "autoscaling/v2"');
82
+ } finally {
83
+ removeDir(imported.dir);
84
+ }
85
+ expect(await roundTrip(yaml)).toEqual(loadAll(yaml));
86
+ });
87
+ });