@intentius/chant-lexicon-helm 0.0.18 → 0.0.22
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/integrity.json +3 -3
- package/dist/manifest.json +1 -1
- package/dist/skills/chant-helm-create-chart.md +1 -1
- package/package.json +21 -3
- package/src/codegen/docs.test.ts +14 -0
- package/src/codegen/generate.test.ts +71 -0
- package/src/codegen/package.test.ts +36 -0
- package/src/import/roundtrip.test.ts +144 -0
- package/src/lint/post-synth/whm101.test.ts +69 -0
- package/src/lint/post-synth/whm102.test.ts +57 -0
- package/src/lint/post-synth/whm103.test.ts +58 -0
- package/src/lint/post-synth/whm104.test.ts +57 -0
- package/src/lint/post-synth/whm105.test.ts +41 -0
- package/src/lint/post-synth/whm201.test.ts +59 -0
- package/src/lint/post-synth/whm202.test.ts +62 -0
- package/src/lint/post-synth/whm203.test.ts +58 -0
- package/src/lint/post-synth/whm204.test.ts +56 -0
- package/src/lint/post-synth/whm301.test.ts +49 -0
- package/src/lint/post-synth/whm302.test.ts +58 -0
- package/src/lint/post-synth/whm401.test.ts +59 -0
- package/src/lint/post-synth/whm402.test.ts +58 -0
- package/src/lint/post-synth/whm403.test.ts +50 -0
- package/src/lint/post-synth/whm404.test.ts +50 -0
- package/src/lint/post-synth/whm405.test.ts +60 -0
- package/src/lint/post-synth/whm406.test.ts +43 -0
- package/src/lint/post-synth/whm407.test.ts +60 -0
- package/src/lint/post-synth/whm501.test.ts +70 -0
- package/src/lint/post-synth/whm502.test.ts +72 -0
- package/src/lint/rules/chart-metadata.test.ts +45 -0
- package/src/lint/rules/no-hardcoded-image.test.ts +41 -0
- package/src/lint/rules/values-no-secrets.test.ts +48 -0
- package/src/plugin.ts +205 -0
- package/src/skills/create-chart.md +1 -1
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "xxhash64",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "21f64ba92bab7858",
|
|
5
5
|
"meta.json": "b7aab243e162dfaf",
|
|
6
6
|
"types/index.d.ts": "e8011da51963f058",
|
|
7
7
|
"rules/no-hardcoded-image.ts": "c75aa9c33016c3b9",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"rules/whm501.ts": "e9a9f2b4e034a51f",
|
|
31
31
|
"skills/chant-helm-chart-patterns.md": "7a2163247f44bf6d",
|
|
32
32
|
"skills/chant-helm-chart-security-patterns.md": "a7b950513dac7d37",
|
|
33
|
-
"skills/chant-helm-create-chart.md": "
|
|
33
|
+
"skills/chant-helm-create-chart.md": "52df5756e29c32bf"
|
|
34
34
|
},
|
|
35
|
-
"composite": "
|
|
35
|
+
"composite": "987b6dfc47cf4f5d"
|
|
36
36
|
}
|
package/dist/manifest.json
CHANGED
|
@@ -8,7 +8,7 @@ user-invocable: true
|
|
|
8
8
|
|
|
9
9
|
## How chant and Helm relate
|
|
10
10
|
|
|
11
|
-
chant is a **synthesis
|
|
11
|
+
chant is a **synthesis compiler** — it compiles TypeScript source files into a complete Helm chart directory (Chart.yaml, values.yaml, templates/, etc.). `chant build` does not call the Helm CLI; synthesis is pure and deterministic. Your job as an agent is to bridge synthesis and deployment:
|
|
12
12
|
|
|
13
13
|
- Use **chant** for: build, lint, diff (local chart comparison)
|
|
14
14
|
- Use **helm** for: install, upgrade, rollback, test, dependency update
|
package/package.json
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-helm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
|
+
"description": "Helm chart lexicon for chant — declarative IaC in TypeScript",
|
|
4
5
|
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://intentius.io/chant",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/intentius/chant.git",
|
|
10
|
+
"directory": "lexicons/helm"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/intentius/chant/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"infrastructure-as-code",
|
|
17
|
+
"iac",
|
|
18
|
+
"typescript",
|
|
19
|
+
"helm",
|
|
20
|
+
"kubernetes",
|
|
21
|
+
"chant"
|
|
22
|
+
],
|
|
5
23
|
"type": "module",
|
|
6
24
|
"files": [
|
|
7
25
|
"src/",
|
|
@@ -25,8 +43,8 @@
|
|
|
25
43
|
"prepack": "bun run generate && bun run bundle && bun run validate"
|
|
26
44
|
},
|
|
27
45
|
"dependencies": {
|
|
28
|
-
"@intentius/chant": "0.0.
|
|
29
|
-
"@intentius/chant-lexicon-k8s": "0.0.
|
|
46
|
+
"@intentius/chant": "0.0.22",
|
|
47
|
+
"@intentius/chant-lexicon-k8s": "0.0.22"
|
|
30
48
|
},
|
|
31
49
|
"devDependencies": {
|
|
32
50
|
"typescript": "^5.9.3"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
describe("Helm docs generation", () => {
|
|
4
|
+
test("docs module is importable", async () => {
|
|
5
|
+
const mod = await import("./docs");
|
|
6
|
+
expect(mod.generateDocs).toBeFunction();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("generateDocs function signature accepts options", async () => {
|
|
10
|
+
const mod = await import("./docs");
|
|
11
|
+
// Verify the function exists and has the expected shape
|
|
12
|
+
expect(typeof mod.generateDocs).toBe("function");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { generate } from "./generate";
|
|
3
|
+
|
|
4
|
+
describe("Helm generate pipeline", () => {
|
|
5
|
+
test("generate module is importable", async () => {
|
|
6
|
+
const mod = await import("./generate");
|
|
7
|
+
expect(mod.generate).toBeFunction();
|
|
8
|
+
expect(mod.writeGeneratedFiles).toBeFunction();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("generates lexicon JSON, types, and index", async () => {
|
|
12
|
+
const result = await generate();
|
|
13
|
+
expect(result.lexiconJSON).toBeTruthy();
|
|
14
|
+
expect(result.typesDTS).toBeTruthy();
|
|
15
|
+
expect(result.indexTS).toBeTruthy();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("lexicon JSON contains known resource types", async () => {
|
|
19
|
+
const result = await generate();
|
|
20
|
+
const registry = JSON.parse(result.lexiconJSON);
|
|
21
|
+
expect(registry.Chart).toBeDefined();
|
|
22
|
+
expect(registry.Chart.resourceType).toBe("Helm::Chart");
|
|
23
|
+
expect(registry.Chart.kind).toBe("resource");
|
|
24
|
+
expect(registry.Values).toBeDefined();
|
|
25
|
+
expect(registry.HelmHook).toBeDefined();
|
|
26
|
+
expect(registry.HelmHook.kind).toBe("property");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("types DTS contains class declarations", async () => {
|
|
30
|
+
const result = await generate();
|
|
31
|
+
expect(result.typesDTS).toContain("export interface ChartProps");
|
|
32
|
+
expect(result.typesDTS).toContain("export declare const Chart");
|
|
33
|
+
expect(result.typesDTS).toContain("export interface ValuesProps");
|
|
34
|
+
expect(result.typesDTS).toContain("export declare const Values");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("runtime index contains createResource calls", async () => {
|
|
38
|
+
const result = await generate();
|
|
39
|
+
expect(result.indexTS).toContain('createResource("Helm::Chart"');
|
|
40
|
+
expect(result.indexTS).toContain('createResource("Helm::Values"');
|
|
41
|
+
expect(result.indexTS).toContain('createProperty("Helm::Hook"');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("resource and property counts are correct", async () => {
|
|
45
|
+
const result = await generate();
|
|
46
|
+
expect(result.resources).toBeGreaterThan(0);
|
|
47
|
+
expect(result.properties).toBeGreaterThan(0);
|
|
48
|
+
expect(result.enums).toBe(0);
|
|
49
|
+
expect(result.warnings).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("lexicon JSON contains all Helm types", async () => {
|
|
53
|
+
const result = await generate();
|
|
54
|
+
const registry = JSON.parse(result.lexiconJSON);
|
|
55
|
+
expect(registry.HelmTest).toBeDefined();
|
|
56
|
+
expect(registry.HelmNotes).toBeDefined();
|
|
57
|
+
expect(registry.HelmDependency).toBeDefined();
|
|
58
|
+
expect(registry.HelmMaintainer).toBeDefined();
|
|
59
|
+
expect(registry.HelmCRD).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("Chart props include required fields", async () => {
|
|
63
|
+
const result = await generate();
|
|
64
|
+
const registry = JSON.parse(result.lexiconJSON);
|
|
65
|
+
const chartProps = registry.Chart.props;
|
|
66
|
+
expect(chartProps.apiVersion).toBeDefined();
|
|
67
|
+
expect(chartProps.apiVersion.required).toBe(true);
|
|
68
|
+
expect(chartProps.name.required).toBe(true);
|
|
69
|
+
expect(chartProps.version.required).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { packageLexicon } from "./package";
|
|
3
|
+
|
|
4
|
+
describe("Helm package pipeline", () => {
|
|
5
|
+
test("packageLexicon is importable", async () => {
|
|
6
|
+
const mod = await import("./package");
|
|
7
|
+
expect(mod.packageLexicon).toBeFunction();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("packageLexicon returns a valid result", async () => {
|
|
11
|
+
const result = await packageLexicon({ verbose: false });
|
|
12
|
+
expect(result).toBeDefined();
|
|
13
|
+
expect(result.spec).toBeDefined();
|
|
14
|
+
expect(result.spec.manifest).toBeDefined();
|
|
15
|
+
expect(result.spec.manifest.name).toBe("helm");
|
|
16
|
+
expect(result.spec.manifest.namespace).toBe("Helm");
|
|
17
|
+
expect(result.spec.manifest.chantVersion).toBeTruthy();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("manifest contains intrinsics", async () => {
|
|
21
|
+
const result = await packageLexicon();
|
|
22
|
+
const intrinsics = result.spec.manifest.intrinsics;
|
|
23
|
+
expect(intrinsics).toBeDefined();
|
|
24
|
+
expect(intrinsics!.length).toBeGreaterThan(0);
|
|
25
|
+
const names = intrinsics!.map((i: { name: string }) => i.name);
|
|
26
|
+
expect(names).toContain("values");
|
|
27
|
+
expect(names).toContain("Release");
|
|
28
|
+
expect(names).toContain("include");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("stats have resource and property counts", async () => {
|
|
32
|
+
const result = await packageLexicon();
|
|
33
|
+
expect(result.stats.resources).toBeGreaterThan(0);
|
|
34
|
+
expect(result.stats.properties).toBeGreaterThan(0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { HelmParser } from "./parser";
|
|
3
|
+
import { HelmGenerator } from "./generator";
|
|
4
|
+
|
|
5
|
+
const parser = new HelmParser();
|
|
6
|
+
const generator = new HelmGenerator();
|
|
7
|
+
|
|
8
|
+
describe("roundtrip: parse -> generate", () => {
|
|
9
|
+
test("minimal chart roundtrip", () => {
|
|
10
|
+
const files = {
|
|
11
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
12
|
+
};
|
|
13
|
+
const ir = parser.parseFiles(files);
|
|
14
|
+
const generated = generator.generate(ir);
|
|
15
|
+
|
|
16
|
+
expect(generated).toHaveLength(1);
|
|
17
|
+
expect(generated[0].path).toBe("chart.ts");
|
|
18
|
+
expect(generated[0].content).toContain("import");
|
|
19
|
+
expect(generated[0].content).toContain("export const chart = new Chart(");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("chart with values roundtrip", () => {
|
|
23
|
+
const files = {
|
|
24
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
25
|
+
"values.yaml": "replicaCount: 2\nimage:\n repository: nginx\n tag: latest\n",
|
|
26
|
+
};
|
|
27
|
+
const ir = parser.parseFiles(files);
|
|
28
|
+
expect(ir.parameters.length).toBeGreaterThan(0);
|
|
29
|
+
|
|
30
|
+
const generated = generator.generate(ir);
|
|
31
|
+
const content = generated[0].content;
|
|
32
|
+
expect(content).toContain("Chart");
|
|
33
|
+
expect(content).toContain("Values");
|
|
34
|
+
expect(content).toContain("export const valuesSchema = new Values(");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("Deployment template roundtrip", () => {
|
|
38
|
+
const files = {
|
|
39
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
40
|
+
"templates/deploy.yaml": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: my-app\nspec:\n replicas: 1\n",
|
|
41
|
+
};
|
|
42
|
+
const ir = parser.parseFiles(files);
|
|
43
|
+
const deploy = ir.resources.find((r) => r.type === "K8s::Apps::Deployment");
|
|
44
|
+
expect(deploy).toBeDefined();
|
|
45
|
+
|
|
46
|
+
const generated = generator.generate(ir);
|
|
47
|
+
const content = generated[0].content;
|
|
48
|
+
expect(content).toContain("@intentius/chant-lexicon-k8s");
|
|
49
|
+
expect(content).toContain("Deployment");
|
|
50
|
+
expect(content).toContain("export const");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("Service template roundtrip", () => {
|
|
54
|
+
const files = {
|
|
55
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
56
|
+
"templates/svc.yaml": "apiVersion: v1\nkind: Service\nmetadata:\n name: my-app\nspec:\n type: ClusterIP\n ports:\n - port: 80\n",
|
|
57
|
+
};
|
|
58
|
+
const ir = parser.parseFiles(files);
|
|
59
|
+
const svc = ir.resources.find((r) => r.type === "K8s::Core::Service");
|
|
60
|
+
expect(svc).toBeDefined();
|
|
61
|
+
|
|
62
|
+
const generated = generator.generate(ir);
|
|
63
|
+
const content = generated[0].content;
|
|
64
|
+
expect(content).toContain("Service");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("template expressions produce intrinsic imports", () => {
|
|
68
|
+
const files = {
|
|
69
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
70
|
+
"templates/deploy.yaml": 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: {{ include "my-app.fullname" . }}\nspec:\n replicas: {{ .Values.replicaCount }}\n',
|
|
71
|
+
};
|
|
72
|
+
const ir = parser.parseFiles(files);
|
|
73
|
+
const generated = generator.generate(ir);
|
|
74
|
+
const content = generated[0].content;
|
|
75
|
+
expect(content).toContain("include");
|
|
76
|
+
expect(content).toContain("values");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("NOTES.txt roundtrip", () => {
|
|
80
|
+
const files = {
|
|
81
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
82
|
+
"templates/NOTES.txt": "Thank you for installing {{ .Chart.Name }}",
|
|
83
|
+
};
|
|
84
|
+
const ir = parser.parseFiles(files);
|
|
85
|
+
const notes = ir.resources.find((r) => r.type === "Helm::Notes");
|
|
86
|
+
expect(notes).toBeDefined();
|
|
87
|
+
|
|
88
|
+
const generated = generator.generate(ir);
|
|
89
|
+
expect(generated[0].content).toContain("HelmNotes");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("full chart with multiple resources roundtrip", () => {
|
|
93
|
+
const files = {
|
|
94
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
95
|
+
"values.yaml": "replicaCount: 1\nimage:\n repository: nginx\n tag: stable\n",
|
|
96
|
+
"templates/deploy.yaml": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: my-app\nspec:\n replicas: {{ .Values.replicaCount }}\n",
|
|
97
|
+
"templates/svc.yaml": "apiVersion: v1\nkind: Service\nmetadata:\n name: my-app\nspec:\n type: ClusterIP\n",
|
|
98
|
+
"templates/NOTES.txt": "Thank you for installing my-app!",
|
|
99
|
+
};
|
|
100
|
+
const ir = parser.parseFiles(files);
|
|
101
|
+
expect(ir.resources.length).toBeGreaterThanOrEqual(4);
|
|
102
|
+
|
|
103
|
+
const generated = generator.generate(ir);
|
|
104
|
+
const content = generated[0].content;
|
|
105
|
+
expect(content).toContain("Chart");
|
|
106
|
+
expect(content).toContain("Values");
|
|
107
|
+
expect(content).toContain("Deployment");
|
|
108
|
+
expect(content).toContain("Service");
|
|
109
|
+
expect(content).toContain("HelmNotes");
|
|
110
|
+
expect(content).toContain("export const");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("IR metadata captures chart name", () => {
|
|
114
|
+
const files = {
|
|
115
|
+
"Chart.yaml": "apiVersion: v2\nname: cool-app\nversion: 1.0.0\n",
|
|
116
|
+
};
|
|
117
|
+
const ir = parser.parseFiles(files);
|
|
118
|
+
expect(ir.metadata?.chartName).toBe("cool-app");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("ConfigMap template roundtrip", () => {
|
|
122
|
+
const files = {
|
|
123
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
124
|
+
"templates/configmap.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: my-config\ndata:\n key: value\n",
|
|
125
|
+
};
|
|
126
|
+
const ir = parser.parseFiles(files);
|
|
127
|
+
const cm = ir.resources.find((r) => r.type === "K8s::Core::ConfigMap");
|
|
128
|
+
expect(cm).toBeDefined();
|
|
129
|
+
|
|
130
|
+
const generated = generator.generate(ir);
|
|
131
|
+
expect(generated[0].content).toContain("ConfigMap");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("skips _helpers.tpl files", () => {
|
|
135
|
+
const files = {
|
|
136
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
137
|
+
"templates/_helpers.tpl": '{{- define "my-app.fullname" -}}\n{{ .Release.Name }}\n{{- end -}}\n',
|
|
138
|
+
};
|
|
139
|
+
const ir = parser.parseFiles(files);
|
|
140
|
+
// Only the chart resource, no template from _helpers.tpl
|
|
141
|
+
expect(ir.resources).toHaveLength(1);
|
|
142
|
+
expect(ir.resources[0].type).toBe("Helm::Chart");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm101 } from "./whm101";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM101: Chart.yaml validation", () => {
|
|
24
|
+
test("passes with valid Chart.yaml", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
});
|
|
28
|
+
expect(whm101.check(ctx)).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("fails when Chart.yaml is missing", () => {
|
|
32
|
+
const ctx = makeCtx({});
|
|
33
|
+
const diags = whm101.check(ctx);
|
|
34
|
+
expect(diags.length).toBeGreaterThan(0);
|
|
35
|
+
expect(diags[0].message).toContain("missing");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("fails when apiVersion is not v2", () => {
|
|
39
|
+
const ctx = makeCtx({
|
|
40
|
+
"Chart.yaml": "apiVersion: v1\nname: test\nversion: 0.1.0\n",
|
|
41
|
+
});
|
|
42
|
+
const diags = whm101.check(ctx);
|
|
43
|
+
expect(diags.some((d) => d.message.includes("v2"))).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("fails when name is missing", () => {
|
|
47
|
+
const ctx = makeCtx({
|
|
48
|
+
"Chart.yaml": "apiVersion: v2\nversion: 0.1.0\n",
|
|
49
|
+
});
|
|
50
|
+
const diags = whm101.check(ctx);
|
|
51
|
+
expect(diags.some((d) => d.message.includes("name"))).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("fails when version is missing", () => {
|
|
55
|
+
const ctx = makeCtx({
|
|
56
|
+
"Chart.yaml": "apiVersion: v2\nname: test\n",
|
|
57
|
+
});
|
|
58
|
+
const diags = whm101.check(ctx);
|
|
59
|
+
expect(diags.some((d) => d.message.includes("version"))).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("all diagnostics have checkId WHM101", () => {
|
|
63
|
+
const ctx = makeCtx({});
|
|
64
|
+
const diags = whm101.check(ctx);
|
|
65
|
+
for (const d of diags) {
|
|
66
|
+
expect(d.checkId).toBe("WHM101");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm102 } from "./whm102";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM102: values.schema.json", () => {
|
|
24
|
+
test("passes when schema exists", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
"values.yaml": "replicaCount: 1\n",
|
|
28
|
+
"values.schema.json": "{}",
|
|
29
|
+
});
|
|
30
|
+
expect(whm102.check(ctx)).toHaveLength(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("warns when values are non-empty but schema is missing", () => {
|
|
34
|
+
const ctx = makeCtx({
|
|
35
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
36
|
+
"values.yaml": "replicaCount: 1\n",
|
|
37
|
+
});
|
|
38
|
+
const diags = whm102.check(ctx);
|
|
39
|
+
expect(diags).toHaveLength(1);
|
|
40
|
+
expect(diags[0].checkId).toBe("WHM102");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("passes when values.yaml is empty object", () => {
|
|
44
|
+
const ctx = makeCtx({
|
|
45
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
46
|
+
"values.yaml": "{}",
|
|
47
|
+
});
|
|
48
|
+
expect(whm102.check(ctx)).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("passes when values.yaml is absent", () => {
|
|
52
|
+
const ctx = makeCtx({
|
|
53
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
54
|
+
});
|
|
55
|
+
expect(whm102.check(ctx)).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm103 } from "./whm103";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM103: template syntax", () => {
|
|
24
|
+
test("passes with balanced braces", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
"templates/deploy.yaml": "name: {{ .Values.name }}\n",
|
|
28
|
+
});
|
|
29
|
+
expect(whm103.check(ctx)).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("fails with unbalanced braces (missing closing)", () => {
|
|
33
|
+
const ctx = makeCtx({
|
|
34
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
35
|
+
"templates/deploy.yaml": "name: {{ .Values.name }\n",
|
|
36
|
+
});
|
|
37
|
+
const diags = whm103.check(ctx);
|
|
38
|
+
expect(diags).toHaveLength(1);
|
|
39
|
+
expect(diags[0].message).toContain("Unbalanced");
|
|
40
|
+
expect(diags[0].checkId).toBe("WHM103");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("passes with multiple balanced expressions", () => {
|
|
44
|
+
const ctx = makeCtx({
|
|
45
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
46
|
+
"templates/deploy.yaml": "name: {{ .Values.name }}\nimage: {{ .Values.image }}\n",
|
|
47
|
+
});
|
|
48
|
+
expect(whm103.check(ctx)).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("ignores non-template files", () => {
|
|
52
|
+
const ctx = makeCtx({
|
|
53
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
54
|
+
"values.yaml": "name: {{ broken\n",
|
|
55
|
+
});
|
|
56
|
+
expect(whm103.check(ctx)).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm104 } from "./whm104";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM104: NOTES.txt", () => {
|
|
24
|
+
test("info when NOTES.txt is missing for application chart", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ntype: application\n",
|
|
27
|
+
});
|
|
28
|
+
const diags = whm104.check(ctx);
|
|
29
|
+
expect(diags).toHaveLength(1);
|
|
30
|
+
expect(diags[0].severity).toBe("info");
|
|
31
|
+
expect(diags[0].checkId).toBe("WHM104");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("passes for library charts without NOTES.txt", () => {
|
|
35
|
+
const ctx = makeCtx({
|
|
36
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ntype: library\n",
|
|
37
|
+
});
|
|
38
|
+
expect(whm104.check(ctx)).toHaveLength(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("passes when NOTES.txt exists", () => {
|
|
42
|
+
const ctx = makeCtx({
|
|
43
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ntype: application\n",
|
|
44
|
+
"templates/NOTES.txt": "Hello!",
|
|
45
|
+
});
|
|
46
|
+
expect(whm104.check(ctx)).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("info when type is absent (defaults to application)", () => {
|
|
50
|
+
const ctx = makeCtx({
|
|
51
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
52
|
+
});
|
|
53
|
+
const diags = whm104.check(ctx);
|
|
54
|
+
expect(diags).toHaveLength(1);
|
|
55
|
+
expect(diags[0].severity).toBe("info");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm105 } from "./whm105";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM105: _helpers.tpl", () => {
|
|
24
|
+
test("warns when _helpers.tpl is missing", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
});
|
|
28
|
+
const diags = whm105.check(ctx);
|
|
29
|
+
expect(diags).toHaveLength(1);
|
|
30
|
+
expect(diags[0].checkId).toBe("WHM105");
|
|
31
|
+
expect(diags[0].severity).toBe("warning");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("passes when _helpers.tpl exists", () => {
|
|
35
|
+
const ctx = makeCtx({
|
|
36
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
37
|
+
"templates/_helpers.tpl": "{{/* helpers */}}",
|
|
38
|
+
});
|
|
39
|
+
expect(whm105.check(ctx)).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
});
|