@intentius/chant-lexicon-helm 0.0.18 → 0.0.24

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.
Files changed (55) hide show
  1. package/dist/integrity.json +5 -5
  2. package/dist/manifest.json +1 -1
  3. package/dist/skills/chant-helm.md +447 -0
  4. package/package.json +21 -3
  5. package/src/codegen/docs.test.ts +14 -0
  6. package/src/codegen/generate.test.ts +71 -0
  7. package/src/codegen/package.test.ts +36 -0
  8. package/src/composites/composites.test.ts +116 -110
  9. package/src/composites/helm-batch-job.ts +33 -19
  10. package/src/composites/helm-crd-lifecycle.ts +37 -24
  11. package/src/composites/helm-cron-job.ts +25 -13
  12. package/src/composites/helm-daemon-set.ts +26 -14
  13. package/src/composites/helm-external-secret.ts +21 -12
  14. package/src/composites/helm-library.ts +21 -7
  15. package/src/composites/helm-microservice.ts +46 -29
  16. package/src/composites/helm-monitored-service.ts +75 -51
  17. package/src/composites/helm-namespace-env.ts +80 -52
  18. package/src/composites/helm-secure-ingress.ts +66 -50
  19. package/src/composites/helm-stateful-service.ts +29 -16
  20. package/src/composites/helm-web-app.ts +37 -22
  21. package/src/composites/helm-worker.ts +34 -20
  22. package/src/import/roundtrip.test.ts +144 -0
  23. package/src/lint/post-synth/whm101.test.ts +69 -0
  24. package/src/lint/post-synth/whm102.test.ts +57 -0
  25. package/src/lint/post-synth/whm103.test.ts +58 -0
  26. package/src/lint/post-synth/whm104.test.ts +57 -0
  27. package/src/lint/post-synth/whm105.test.ts +41 -0
  28. package/src/lint/post-synth/whm201.test.ts +59 -0
  29. package/src/lint/post-synth/whm202.test.ts +62 -0
  30. package/src/lint/post-synth/whm203.test.ts +58 -0
  31. package/src/lint/post-synth/whm204.test.ts +56 -0
  32. package/src/lint/post-synth/whm301.test.ts +49 -0
  33. package/src/lint/post-synth/whm302.test.ts +58 -0
  34. package/src/lint/post-synth/whm401.test.ts +59 -0
  35. package/src/lint/post-synth/whm402.test.ts +58 -0
  36. package/src/lint/post-synth/whm403.test.ts +50 -0
  37. package/src/lint/post-synth/whm404.test.ts +50 -0
  38. package/src/lint/post-synth/whm405.test.ts +60 -0
  39. package/src/lint/post-synth/whm406.test.ts +43 -0
  40. package/src/lint/post-synth/whm407.test.ts +60 -0
  41. package/src/lint/post-synth/whm501.test.ts +70 -0
  42. package/src/lint/post-synth/whm502.test.ts +72 -0
  43. package/src/lint/rules/chart-metadata.test.ts +45 -0
  44. package/src/lint/rules/no-hardcoded-image.test.ts +41 -0
  45. package/src/lint/rules/values-no-secrets.test.ts +48 -0
  46. package/src/plugin.test.ts +3 -3
  47. package/src/plugin.ts +190 -19
  48. package/src/resources.ts +29 -0
  49. package/src/skills/chant-helm.md +447 -0
  50. package/dist/skills/chant-helm-create-chart.md +0 -211
  51. package/src/skills/create-chart.md +0 -211
  52. /package/dist/skills/{chant-helm-chart-patterns.md → chant-helm-patterns.md} +0 -0
  53. /package/dist/skills/{chant-helm-chart-security-patterns.md → chant-helm-security.md} +0 -0
  54. /package/src/skills/{chart-patterns.md → chant-helm-patterns.md} +0 -0
  55. /package/src/skills/{chart-security-patterns.md → chant-helm-security.md} +0 -0
@@ -29,6 +29,11 @@ function hasIntrinsic(obj: unknown): boolean {
29
29
  return false;
30
30
  }
31
31
 
32
+ /** Access a member's props (Declarable stores data in .props). */
33
+ function p(member: unknown): any {
34
+ return (member as any).props;
35
+ }
36
+
32
37
  describe("HelmWebApp", () => {
33
38
  test("returns chart, values, deployment, and service", () => {
34
39
  const result = HelmWebApp({ name: "my-app" });
@@ -40,14 +45,14 @@ describe("HelmWebApp", () => {
40
45
 
41
46
  test("chart has correct metadata", () => {
42
47
  const result = HelmWebApp({ name: "web-ui" });
43
- expect(result.chart.name).toBe("web-ui");
44
- expect(result.chart.apiVersion).toBe("v2");
45
- expect(result.chart.type).toBe("application");
48
+ expect(p(result.chart).name).toBe("web-ui");
49
+ expect(p(result.chart).apiVersion).toBe("v2");
50
+ expect(p(result.chart).type).toBe("application");
46
51
  });
47
52
 
48
53
  test("values include default image and service config", () => {
49
54
  const result = HelmWebApp({ name: "app" });
50
- const vals = result.values as any;
55
+ const vals = p(result.values);
51
56
  expect(vals.image.repository).toBe("nginx");
52
57
  expect(vals.service.port).toBe(80);
53
58
  expect(vals.replicaCount).toBe(1);
@@ -60,7 +65,7 @@ describe("HelmWebApp", () => {
60
65
  port: 3000,
61
66
  replicas: 3,
62
67
  });
63
- const vals = result.values as any;
68
+ const vals = p(result.values);
64
69
  expect(vals.image.repository).toBe("myregistry/api");
65
70
  expect(vals.service.port).toBe(3000);
66
71
  expect(vals.replicaCount).toBe(3);
@@ -82,12 +87,12 @@ describe("HelmWebApp", () => {
82
87
 
83
88
  test("deployment uses Helm intrinsics", () => {
84
89
  const result = HelmWebApp({ name: "app" });
85
- expect(hasIntrinsic(result.deployment)).toBe(true);
90
+ expect(hasIntrinsic(p(result.deployment))).toBe(true);
86
91
  });
87
92
 
88
93
  test("omitted security/scheduling props produce no values change", () => {
89
94
  const result = HelmWebApp({ name: "app" });
90
- const vals = result.values as any;
95
+ const vals = p(result.values);
91
96
  expect(vals.podSecurityContext).toBeUndefined();
92
97
  expect(vals.securityContext).toBeUndefined();
93
98
  expect(vals.nodeSelector).toBeUndefined();
@@ -105,7 +110,7 @@ describe("HelmWebApp", () => {
105
110
  podSecurityContext: { runAsNonRoot: true },
106
111
  securityContext: { readOnlyRootFilesystem: true },
107
112
  });
108
- const vals = result.values as any;
113
+ const vals = p(result.values);
109
114
  expect(vals.podSecurityContext).toEqual({ runAsNonRoot: true });
110
115
  expect(vals.securityContext).toEqual({ readOnlyRootFilesystem: true });
111
116
  });
@@ -117,7 +122,7 @@ describe("HelmWebApp", () => {
117
122
  tolerations: [{ key: "special", operator: "Exists" }],
118
123
  affinity: { nodeAffinity: {} },
119
124
  });
120
- const vals = result.values as any;
125
+ const vals = p(result.values);
121
126
  expect(vals.nodeSelector).toEqual({ "kubernetes.io/os": "linux" });
122
127
  expect(vals.tolerations).toHaveLength(1);
123
128
  expect(vals.affinity).toBeDefined();
@@ -130,7 +135,7 @@ describe("HelmWebApp", () => {
130
135
  readinessProbe: { httpGet: { path: "/readyz", port: "http" } },
131
136
  strategy: { type: "RollingUpdate" },
132
137
  });
133
- const vals = result.values as any;
138
+ const vals = p(result.values);
134
139
  expect(vals.livenessProbe).toBeDefined();
135
140
  expect(vals.readinessProbe).toBeDefined();
136
141
  expect(vals.strategy).toBeDefined();
@@ -141,7 +146,7 @@ describe("HelmWebApp", () => {
141
146
  name: "app",
142
147
  podAnnotations: { "prometheus.io/scrape": "true" },
143
148
  });
144
- const vals = result.values as any;
149
+ const vals = p(result.values);
145
150
  expect(vals.podAnnotations).toEqual({ "prometheus.io/scrape": "true" });
146
151
  });
147
152
 
@@ -150,7 +155,7 @@ describe("HelmWebApp", () => {
150
155
  name: "app",
151
156
  nodeSelector: { "kubernetes.io/os": "linux" },
152
157
  });
153
- const podSpec = (result.deployment as any).spec.template.spec;
158
+ const podSpec = p(result.deployment).spec.template.spec;
154
159
  expect(hasIntrinsic(podSpec.nodeSelector)).toBe(true);
155
160
  });
156
161
  });
@@ -166,30 +171,30 @@ describe("HelmStatefulService", () => {
166
171
 
167
172
  test("chart is marked as application", () => {
168
173
  const result = HelmStatefulService({ name: "db" });
169
- expect(result.chart.type).toBe("application");
174
+ expect(p(result.chart).type).toBe("application");
170
175
  });
171
176
 
172
177
  test("service is headless (clusterIP: None)", () => {
173
178
  const result = HelmStatefulService({ name: "db" });
174
- expect((result.service as any).spec.clusterIP).toBe("None");
179
+ expect(p(result.service).spec.clusterIP).toBe("None");
175
180
  });
176
181
 
177
182
  test("statefulSet has volumeClaimTemplates", () => {
178
183
  const result = HelmStatefulService({ name: "db" });
179
- const spec = (result.statefulSet as any).spec;
184
+ const spec = p(result.statefulSet).spec;
180
185
  expect(spec.volumeClaimTemplates).toBeDefined();
181
186
  expect(spec.volumeClaimTemplates).toHaveLength(1);
182
187
  });
183
188
 
184
189
  test("values include persistence config", () => {
185
190
  const result = HelmStatefulService({ name: "db", storageSize: "50Gi" });
186
- const vals = result.values as any;
191
+ const vals = p(result.values);
187
192
  expect(vals.persistence.size).toBe("50Gi");
188
193
  });
189
194
 
190
195
  test("uses Helm intrinsics", () => {
191
196
  const result = HelmStatefulService({ name: "db" });
192
- expect(hasIntrinsic(result.statefulSet)).toBe(true);
197
+ expect(hasIntrinsic(p(result.statefulSet))).toBe(true);
193
198
  });
194
199
 
195
200
  test("no serviceAccount by default", () => {
@@ -200,13 +205,13 @@ describe("HelmStatefulService", () => {
200
205
  test("serviceAccount can be enabled", () => {
201
206
  const result = HelmStatefulService({ name: "db", serviceAccount: true });
202
207
  expect(result.serviceAccount).toBeDefined();
203
- const vals = result.values as any;
208
+ const vals = p(result.values);
204
209
  expect(vals.serviceAccount).toBeDefined();
205
210
  });
206
211
 
207
212
  test("omitted security/scheduling props produce no change", () => {
208
213
  const result = HelmStatefulService({ name: "db" });
209
- const vals = result.values as any;
214
+ const vals = p(result.values);
210
215
  expect(vals.podSecurityContext).toBeUndefined();
211
216
  expect(vals.nodeSelector).toBeUndefined();
212
217
  expect(vals.livenessProbe).toBeUndefined();
@@ -222,7 +227,7 @@ describe("HelmStatefulService", () => {
222
227
  readinessProbe: { tcpSocket: { port: 5432 } },
223
228
  updateStrategy: { type: "RollingUpdate" },
224
229
  });
225
- const vals = result.values as any;
230
+ const vals = p(result.values);
226
231
  expect(vals.podSecurityContext).toBeDefined();
227
232
  expect(vals.nodeSelector).toBeDefined();
228
233
  expect(vals.livenessProbe).toBeDefined();
@@ -241,29 +246,29 @@ describe("HelmCronJob", () => {
241
246
 
242
247
  test("chart has correct name", () => {
243
248
  const result = HelmCronJob({ name: "backup" });
244
- expect(result.chart.name).toBe("backup");
249
+ expect(p(result.chart).name).toBe("backup");
245
250
  });
246
251
 
247
252
  test("default schedule is hourly", () => {
248
253
  const result = HelmCronJob({ name: "job" });
249
- const vals = result.values as any;
254
+ const vals = p(result.values);
250
255
  expect(vals.schedule).toBe("0 * * * *");
251
256
  });
252
257
 
253
258
  test("custom schedule flows through", () => {
254
259
  const result = HelmCronJob({ name: "nightly", schedule: "0 0 * * *" });
255
- const vals = result.values as any;
260
+ const vals = p(result.values);
256
261
  expect(vals.schedule).toBe("0 0 * * *");
257
262
  });
258
263
 
259
264
  test("uses Helm intrinsics", () => {
260
265
  const result = HelmCronJob({ name: "job" });
261
- expect(hasIntrinsic(result.cronJob)).toBe(true);
266
+ expect(hasIntrinsic(p(result.cronJob))).toBe(true);
262
267
  });
263
268
 
264
269
  test("omitted props produce no change", () => {
265
270
  const result = HelmCronJob({ name: "job" });
266
- const vals = result.values as any;
271
+ const vals = p(result.values);
267
272
  expect(vals.podSecurityContext).toBeUndefined();
268
273
  expect(vals.concurrencyPolicy).toBeUndefined();
269
274
  expect(vals.backoffLimit).toBeUndefined();
@@ -278,7 +283,7 @@ describe("HelmCronJob", () => {
278
283
  failedJobsHistoryLimit: 1,
279
284
  backoffLimit: 2,
280
285
  });
281
- const vals = result.values as any;
286
+ const vals = p(result.values);
282
287
  expect(vals.concurrencyPolicy).toBe("Forbid");
283
288
  expect(vals.successfulJobsHistoryLimit).toBe(3);
284
289
  expect(vals.failedJobsHistoryLimit).toBe(1);
@@ -288,7 +293,7 @@ describe("HelmCronJob", () => {
288
293
  test("serviceAccount can be enabled", () => {
289
294
  const result = HelmCronJob({ name: "job", serviceAccount: true });
290
295
  expect(result.serviceAccount).toBeDefined();
291
- const vals = result.values as any;
296
+ const vals = p(result.values);
292
297
  expect(vals.serviceAccount).toBeDefined();
293
298
  });
294
299
 
@@ -299,7 +304,7 @@ describe("HelmCronJob", () => {
299
304
  securityContext: { readOnlyRootFilesystem: true },
300
305
  nodeSelector: { "kubernetes.io/os": "linux" },
301
306
  });
302
- const vals = result.values as any;
307
+ const vals = p(result.values);
303
308
  expect(vals.podSecurityContext).toBeDefined();
304
309
  expect(vals.securityContext).toBeDefined();
305
310
  expect(vals.nodeSelector).toBeDefined();
@@ -340,38 +345,38 @@ describe("HelmMicroservice", () => {
340
345
 
341
346
  test("default port is 8080", () => {
342
347
  const result = HelmMicroservice({ name: "api" });
343
- const vals = result.values as any;
348
+ const vals = p(result.values);
344
349
  expect(vals.service.port).toBe(8080);
345
350
  });
346
351
 
347
352
  test("default replicas is 2", () => {
348
353
  const result = HelmMicroservice({ name: "api" });
349
- const vals = result.values as any;
354
+ const vals = p(result.values);
350
355
  expect(vals.replicaCount).toBe(2);
351
356
  });
352
357
 
353
358
  test("values include health probes", () => {
354
359
  const result = HelmMicroservice({ name: "api" });
355
- const vals = result.values as any;
360
+ const vals = p(result.values);
356
361
  expect(vals.livenessProbe).toBeDefined();
357
362
  expect(vals.readinessProbe).toBeDefined();
358
363
  });
359
364
 
360
365
  test("values include resource limits and requests", () => {
361
366
  const result = HelmMicroservice({ name: "api" });
362
- const vals = result.values as any;
367
+ const vals = p(result.values);
363
368
  expect(vals.resources.limits).toBeDefined();
364
369
  expect(vals.resources.requests).toBeDefined();
365
370
  });
366
371
 
367
372
  test("uses Helm intrinsics", () => {
368
373
  const result = HelmMicroservice({ name: "api" });
369
- expect(hasIntrinsic(result.deployment)).toBe(true);
374
+ expect(hasIntrinsic(p(result.deployment))).toBe(true);
370
375
  });
371
376
 
372
377
  test("omitted security/scheduling props produce no change", () => {
373
378
  const result = HelmMicroservice({ name: "api" });
374
- const vals = result.values as any;
379
+ const vals = p(result.values);
375
380
  expect(vals.podSecurityContext).toBeUndefined();
376
381
  expect(vals.securityContext).toBeUndefined();
377
382
  expect(vals.nodeSelector).toBeUndefined();
@@ -389,7 +394,7 @@ describe("HelmMicroservice", () => {
389
394
  podAnnotations: { "prometheus.io/scrape": "true" },
390
395
  strategy: { type: "RollingUpdate" },
391
396
  });
392
- const vals = result.values as any;
397
+ const vals = p(result.values);
393
398
  expect(vals.podSecurityContext).toBeDefined();
394
399
  expect(vals.securityContext).toBeDefined();
395
400
  expect(vals.nodeSelector).toBeDefined();
@@ -409,19 +414,20 @@ describe("HelmLibrary", () => {
409
414
 
410
415
  test("chart type is library", () => {
411
416
  const result = HelmLibrary({ name: "common" });
412
- expect(result.chart.type).toBe("library");
417
+ expect(p(result.chart).type).toBe("library");
413
418
  });
414
419
 
415
420
  test("default helpers include standard names", () => {
416
421
  const result = HelmLibrary({ name: "common" });
417
- expect(result.helpers).toContain("name");
418
- expect(result.helpers).toContain("fullname");
419
- expect(result.helpers).toContain("labels");
422
+ const helpers = p(result.helpers).helpers;
423
+ expect(helpers).toContain("name");
424
+ expect(helpers).toContain("fullname");
425
+ expect(helpers).toContain("labels");
420
426
  });
421
427
 
422
428
  test("custom helpers override defaults", () => {
423
429
  const result = HelmLibrary({ name: "lib", helpers: ["custom-a", "custom-b"] });
424
- expect(result.helpers).toEqual(["custom-a", "custom-b"]);
430
+ expect(p(result.helpers).helpers).toEqual(["custom-a", "custom-b"]);
425
431
  });
426
432
 
427
433
  test("dependencies are included when provided", () => {
@@ -429,12 +435,12 @@ describe("HelmLibrary", () => {
429
435
  name: "common",
430
436
  dependencies: [{ name: "base", version: "1.x.x", repository: "https://charts.example.com" }],
431
437
  });
432
- expect(result.chart.dependencies).toHaveLength(1);
438
+ expect(p(result.chart).dependencies).toHaveLength(1);
433
439
  });
434
440
 
435
441
  test("no dependencies by default", () => {
436
442
  const result = HelmLibrary({ name: "common" });
437
- expect(result.chart.dependencies).toBeUndefined();
443
+ expect(p(result.chart).dependencies).toBeUndefined();
438
444
  });
439
445
  });
440
446
 
@@ -458,7 +464,7 @@ describe("HelmCRDLifecycle", () => {
458
464
  name: "my-operator",
459
465
  crdContent: "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\n",
460
466
  });
461
- const jobMeta = (result.crdInstallJob as any).metadata;
467
+ const jobMeta = p(result.crdInstallJob).metadata;
462
468
  expect(jobMeta.annotations["helm.sh/hook"]).toBe("pre-install,pre-upgrade");
463
469
  expect(jobMeta.annotations["helm.sh/hook-weight"]).toBe("-5");
464
470
  expect(jobMeta.annotations["helm.sh/hook-delete-policy"]).toBe("before-hook-creation");
@@ -469,7 +475,7 @@ describe("HelmCRDLifecycle", () => {
469
475
  name: "my-operator",
470
476
  crdContent: "crd content",
471
477
  });
472
- const rules = (result.clusterRole as any).rules;
478
+ const rules = p(result.clusterRole).rules;
473
479
  expect(rules).toHaveLength(1);
474
480
  expect(rules[0].apiGroups).toContain("apiextensions.k8s.io");
475
481
  expect(rules[0].resources).toContain("customresourcedefinitions");
@@ -481,7 +487,7 @@ describe("HelmCRDLifecycle", () => {
481
487
  name: "my-operator",
482
488
  crdContent,
483
489
  });
484
- expect((result.crdConfigMap as any).data["crds.yaml"]).toBe(crdContent);
490
+ expect(p(result.crdConfigMap).data["crds.yaml"]).toBe(crdContent);
485
491
  });
486
492
 
487
493
  test("uses Helm intrinsics", () => {
@@ -489,8 +495,8 @@ describe("HelmCRDLifecycle", () => {
489
495
  name: "my-operator",
490
496
  crdContent: "crd",
491
497
  });
492
- expect(hasIntrinsic(result.crdInstallJob)).toBe(true);
493
- expect(hasIntrinsic(result.clusterRoleBinding)).toBe(true);
498
+ expect(hasIntrinsic(p(result.crdInstallJob))).toBe(true);
499
+ expect(hasIntrinsic(p(result.clusterRoleBinding))).toBe(true);
494
500
  });
495
501
 
496
502
  test("custom kubectl image flows through to values", () => {
@@ -500,7 +506,7 @@ describe("HelmCRDLifecycle", () => {
500
506
  kubectlImage: "custom/kubectl",
501
507
  kubectlTag: "1.28",
502
508
  });
503
- const vals = result.values as any;
509
+ const vals = p(result.values);
504
510
  expect(vals.crdLifecycle.kubectl.image).toBe("custom/kubectl");
505
511
  expect(vals.crdLifecycle.kubectl.tag).toBe("1.28");
506
512
  });
@@ -526,13 +532,13 @@ describe("HelmDaemonSet", () => {
526
532
 
527
533
  test("DaemonSet has RollingUpdate strategy in values", () => {
528
534
  const result = HelmDaemonSet({ name: "log-agent" });
529
- const vals = result.values as any;
535
+ const vals = p(result.values);
530
536
  expect(vals.updateStrategy.type).toBe("RollingUpdate");
531
537
  });
532
538
 
533
539
  test("default image is fluent-bit", () => {
534
540
  const result = HelmDaemonSet({ name: "log-agent" });
535
- const vals = result.values as any;
541
+ const vals = p(result.values);
536
542
  expect(vals.image.repository).toBe("fluent/fluent-bit");
537
543
  });
538
544
 
@@ -543,19 +549,19 @@ describe("HelmDaemonSet", () => {
543
549
  imageTag: "v1.6.0",
544
550
  port: 9100,
545
551
  });
546
- const vals = result.values as any;
552
+ const vals = p(result.values);
547
553
  expect(vals.image.repository).toBe("prom/node-exporter");
548
554
  expect(vals.image.tag).toBe("v1.6.0");
549
555
  });
550
556
 
551
557
  test("uses Helm intrinsics", () => {
552
558
  const result = HelmDaemonSet({ name: "agent" });
553
- expect(hasIntrinsic(result.daemonSet)).toBe(true);
559
+ expect(hasIntrinsic(p(result.daemonSet))).toBe(true);
554
560
  });
555
561
 
556
562
  test("nodeSelector uses With() intrinsic", () => {
557
563
  const result = HelmDaemonSet({ name: "agent" });
558
- const podSpec = (result.daemonSet as any).spec.template.spec;
564
+ const podSpec = p(result.daemonSet).spec.template.spec;
559
565
  expect(hasIntrinsic(podSpec.nodeSelector)).toBe(true);
560
566
  });
561
567
  });
@@ -576,7 +582,7 @@ describe("HelmWorker", () => {
576
582
 
577
583
  test("default replicas is 2", () => {
578
584
  const result = HelmWorker({ name: "job-processor" });
579
- const vals = result.values as any;
585
+ const vals = p(result.values);
580
586
  expect(vals.replicaCount).toBe(2);
581
587
  });
582
588
 
@@ -593,27 +599,27 @@ describe("HelmWorker", () => {
593
599
  test("can enable autoscaling", () => {
594
600
  const result = HelmWorker({ name: "job-processor", autoscaling: true });
595
601
  expect(result.hpa).toBeDefined();
596
- const vals = result.values as any;
602
+ const vals = p(result.values);
597
603
  expect(vals.autoscaling).toBeDefined();
598
604
  });
599
605
 
600
606
  test("values include queue config", () => {
601
607
  const result = HelmWorker({ name: "job-processor" });
602
- const vals = result.values as any;
608
+ const vals = p(result.values);
603
609
  expect(vals.queue).toBeDefined();
604
610
  expect(vals.queue.concurrency).toBe(5);
605
611
  });
606
612
 
607
613
  test("uses exec-based probes", () => {
608
614
  const result = HelmWorker({ name: "job-processor" });
609
- const vals = result.values as any;
615
+ const vals = p(result.values);
610
616
  expect(vals.livenessProbe.exec).toBeDefined();
611
617
  expect(vals.readinessProbe.exec).toBeDefined();
612
618
  });
613
619
 
614
620
  test("uses Helm intrinsics", () => {
615
621
  const result = HelmWorker({ name: "job-processor" });
616
- expect(hasIntrinsic(result.deployment)).toBe(true);
622
+ expect(hasIntrinsic(p(result.deployment))).toBe(true);
617
623
  });
618
624
  });
619
625
 
@@ -635,8 +641,8 @@ describe("HelmExternalSecret", () => {
635
641
  secretStoreName: "vault",
636
642
  data: { API_KEY: "secret/data/api-key" },
637
643
  });
638
- expect((result.externalSecret as any).apiVersion).toBe("external-secrets.io/v1beta1");
639
- expect((result.externalSecret as any).kind).toBe("ExternalSecret");
644
+ expect(p(result.externalSecret).apiVersion).toBe("external-secrets.io/v1beta1");
645
+ expect(p(result.externalSecret).kind).toBe("ExternalSecret");
640
646
  });
641
647
 
642
648
  test("data maps to secretKey/remoteRef format", () => {
@@ -648,7 +654,7 @@ describe("HelmExternalSecret", () => {
648
654
  API_KEY: "secret/data/api-key",
649
655
  },
650
656
  });
651
- const spec = (result.externalSecret as any).spec;
657
+ const spec = p(result.externalSecret).spec;
652
658
  expect(spec.data).toHaveLength(2);
653
659
  expect(spec.data[0].secretKey).toBe("DB_PASSWORD");
654
660
  expect(spec.data[0].remoteRef.key).toBe("secret/data/db-password");
@@ -660,7 +666,7 @@ describe("HelmExternalSecret", () => {
660
666
  secretStoreName: "vault",
661
667
  data: { KEY: "path" },
662
668
  });
663
- const vals = result.values as any;
669
+ const vals = p(result.values);
664
670
  expect(vals.externalSecret.secretStore.kind).toBe("ClusterSecretStore");
665
671
  });
666
672
 
@@ -671,7 +677,7 @@ describe("HelmExternalSecret", () => {
671
677
  data: { KEY: "path" },
672
678
  refreshInterval: "30m",
673
679
  });
674
- const vals = result.values as any;
680
+ const vals = p(result.values);
675
681
  expect(vals.externalSecret.refreshInterval).toBe("30m");
676
682
  });
677
683
 
@@ -681,7 +687,7 @@ describe("HelmExternalSecret", () => {
681
687
  secretStoreName: "vault",
682
688
  data: { KEY: "path" },
683
689
  });
684
- expect(hasIntrinsic(result.externalSecret)).toBe(true);
690
+ expect(hasIntrinsic(p(result.externalSecret))).toBe(true);
685
691
  });
686
692
  });
687
693
 
@@ -697,15 +703,15 @@ describe("HelmBatchJob", () => {
697
703
 
698
704
  test("chart has correct metadata", () => {
699
705
  const result = HelmBatchJob({ name: "migrate" });
700
- expect(result.chart.name).toBe("migrate");
701
- expect(result.chart.apiVersion).toBe("v2");
702
- expect(result.chart.type).toBe("application");
706
+ expect(p(result.chart).name).toBe("migrate");
707
+ expect(p(result.chart).apiVersion).toBe("v2");
708
+ expect(p(result.chart).type).toBe("application");
703
709
  });
704
710
 
705
711
  test("includes serviceAccount by default", () => {
706
712
  const result = HelmBatchJob({ name: "migrate" });
707
713
  expect(result.serviceAccount).toBeDefined();
708
- const vals = result.values as any;
714
+ const vals = p(result.values);
709
715
  expect(vals.serviceAccount).toBeDefined();
710
716
  });
711
717
 
@@ -724,16 +730,16 @@ describe("HelmBatchJob", () => {
724
730
  const result = HelmBatchJob({ name: "migrate", rbac: true });
725
731
  expect(result.role).toBeDefined();
726
732
  expect(result.roleBinding).toBeDefined();
727
- expect((result.role as any).kind).toBe("Role");
728
- expect((result.roleBinding as any).kind).toBe("RoleBinding");
729
- const vals = result.values as any;
733
+ expect(p(result.role).kind).toBe("Role");
734
+ expect(p(result.roleBinding).kind).toBe("RoleBinding");
735
+ const vals = p(result.values);
730
736
  expect(vals.rbac).toBeDefined();
731
737
  expect(vals.rbac.rules).toEqual([]);
732
738
  });
733
739
 
734
740
  test("default job settings", () => {
735
741
  const result = HelmBatchJob({ name: "migrate" });
736
- const vals = result.values as any;
742
+ const vals = p(result.values);
737
743
  expect(vals.job.backoffLimit).toBe(6);
738
744
  expect(vals.job.completions).toBe(1);
739
745
  expect(vals.job.parallelism).toBe(1);
@@ -749,7 +755,7 @@ describe("HelmBatchJob", () => {
749
755
  restartPolicy: "Never",
750
756
  ttlSecondsAfterFinished: 300,
751
757
  });
752
- const vals = result.values as any;
758
+ const vals = p(result.values);
753
759
  expect(vals.job.backoffLimit).toBe(3);
754
760
  expect(vals.job.completions).toBe(5);
755
761
  expect(vals.job.parallelism).toBe(2);
@@ -759,14 +765,14 @@ describe("HelmBatchJob", () => {
759
765
 
760
766
  test("default image is busybox", () => {
761
767
  const result = HelmBatchJob({ name: "migrate" });
762
- const vals = result.values as any;
768
+ const vals = p(result.values);
763
769
  expect(vals.image.repository).toBe("busybox");
764
770
  expect(vals.image.tag).toBe("latest");
765
771
  });
766
772
 
767
773
  test("uses Helm intrinsics", () => {
768
774
  const result = HelmBatchJob({ name: "migrate" });
769
- expect(hasIntrinsic(result.job)).toBe(true);
775
+ expect(hasIntrinsic(p(result.job))).toBe(true);
770
776
  });
771
777
 
772
778
  test("security props flow through", () => {
@@ -775,7 +781,7 @@ describe("HelmBatchJob", () => {
775
781
  podSecurityContext: { runAsNonRoot: true },
776
782
  securityContext: { readOnlyRootFilesystem: true },
777
783
  });
778
- const vals = result.values as any;
784
+ const vals = p(result.values);
779
785
  expect(vals.podSecurityContext).toBeDefined();
780
786
  expect(vals.securityContext).toBeDefined();
781
787
  });
@@ -786,10 +792,10 @@ describe("HelmBatchJob", () => {
786
792
  nodeSelector: { "kubernetes.io/os": "linux" },
787
793
  tolerations: [{ key: "special", operator: "Exists" }],
788
794
  });
789
- const vals = result.values as any;
795
+ const vals = p(result.values);
790
796
  expect(vals.nodeSelector).toBeDefined();
791
797
  expect(vals.tolerations).toHaveLength(1);
792
- const podSpec = (result.job as any).spec.template.spec;
798
+ const podSpec = p(result.job).spec.template.spec;
793
799
  expect(hasIntrinsic(podSpec.nodeSelector)).toBe(true);
794
800
  });
795
801
  });
@@ -806,8 +812,8 @@ describe("HelmMonitoredService", () => {
806
812
 
807
813
  test("chart has correct metadata", () => {
808
814
  const result = HelmMonitoredService({ name: "api" });
809
- expect(result.chart.name).toBe("api");
810
- expect(result.chart.type).toBe("application");
815
+ expect(p(result.chart).name).toBe("api");
816
+ expect(p(result.chart).type).toBe("application");
811
817
  });
812
818
 
813
819
  test("includes serviceAccount by default", () => {
@@ -828,14 +834,14 @@ describe("HelmMonitoredService", () => {
828
834
  test("PrometheusRule created when alertRules enabled", () => {
829
835
  const result = HelmMonitoredService({ name: "api", alertRules: true });
830
836
  expect(result.prometheusRule).toBeDefined();
831
- const vals = result.values as any;
837
+ const vals = p(result.values);
832
838
  expect(vals.alerting).toBeDefined();
833
839
  expect(vals.alerting.rules).toEqual([]);
834
840
  });
835
841
 
836
842
  test("default monitoring config", () => {
837
843
  const result = HelmMonitoredService({ name: "api" });
838
- const vals = result.values as any;
844
+ const vals = p(result.values);
839
845
  expect(vals.monitoring.enabled).toBe(true);
840
846
  expect(vals.monitoring.metricsPort).toBe(9090);
841
847
  expect(vals.monitoring.metricsPath).toBe("/metrics");
@@ -849,7 +855,7 @@ describe("HelmMonitoredService", () => {
849
855
  metricsPath: "/actuator/prometheus",
850
856
  scrapeInterval: "15s",
851
857
  });
852
- const vals = result.values as any;
858
+ const vals = p(result.values);
853
859
  expect(vals.monitoring.metricsPort).toBe(8081);
854
860
  expect(vals.monitoring.metricsPath).toBe("/actuator/prometheus");
855
861
  expect(vals.monitoring.scrapeInterval).toBe("15s");
@@ -857,7 +863,7 @@ describe("HelmMonitoredService", () => {
857
863
 
858
864
  test("service exposes both http and metrics ports", () => {
859
865
  const result = HelmMonitoredService({ name: "api" });
860
- const ports = (result.service as any).spec.ports;
866
+ const ports = p(result.service).spec.ports;
861
867
  expect(ports).toHaveLength(2);
862
868
  expect(ports[0].name).toBe("http");
863
869
  expect(ports[1].name).toBe("metrics");
@@ -865,18 +871,18 @@ describe("HelmMonitoredService", () => {
865
871
 
866
872
  test("container has both http and metrics ports", () => {
867
873
  const result = HelmMonitoredService({ name: "api" });
868
- const container = (result.deployment as any).spec.template.spec.containers[0];
874
+ const container = p(result.deployment).spec.template.spec.containers[0];
869
875
  expect(container.ports).toHaveLength(2);
870
876
  });
871
877
 
872
878
  test("serviceMonitor uses Helm intrinsics", () => {
873
879
  const result = HelmMonitoredService({ name: "api" });
874
- expect(hasIntrinsic(result.serviceMonitor)).toBe(true);
880
+ expect(hasIntrinsic(p(result.serviceMonitor))).toBe(true);
875
881
  });
876
882
 
877
883
  test("uses Helm intrinsics in deployment", () => {
878
884
  const result = HelmMonitoredService({ name: "api" });
879
- expect(hasIntrinsic(result.deployment)).toBe(true);
885
+ expect(hasIntrinsic(p(result.deployment))).toBe(true);
880
886
  });
881
887
 
882
888
  test("security and scheduling props flow through", () => {
@@ -886,7 +892,7 @@ describe("HelmMonitoredService", () => {
886
892
  nodeSelector: { "kubernetes.io/os": "linux" },
887
893
  affinity: { nodeAffinity: {} },
888
894
  });
889
- const vals = result.values as any;
895
+ const vals = p(result.values);
890
896
  expect(vals.podSecurityContext).toBeDefined();
891
897
  expect(vals.nodeSelector).toBeDefined();
892
898
  expect(vals.affinity).toBeDefined();
@@ -894,7 +900,7 @@ describe("HelmMonitoredService", () => {
894
900
 
895
901
  test("default replicas is 2", () => {
896
902
  const result = HelmMonitoredService({ name: "api" });
897
- const vals = result.values as any;
903
+ const vals = p(result.values);
898
904
  expect(vals.replicaCount).toBe(2);
899
905
  });
900
906
  });
@@ -910,13 +916,13 @@ describe("HelmSecureIngress", () => {
910
916
 
911
917
  test("chart has correct metadata", () => {
912
918
  const result = HelmSecureIngress({ name: "web" });
913
- expect(result.chart.name).toBe("web");
914
- expect(result.chart.type).toBe("application");
919
+ expect(p(result.chart).name).toBe("web");
920
+ expect(p(result.chart).type).toBe("application");
915
921
  });
916
922
 
917
923
  test("default values include ingress and certManager config", () => {
918
924
  const result = HelmSecureIngress({ name: "web" });
919
- const vals = result.values as any;
925
+ const vals = p(result.values);
920
926
  expect(vals.ingress.enabled).toBe(true);
921
927
  expect(vals.ingress.tls.enabled).toBe(true);
922
928
  expect(vals.certManager.enabled).toBe(true);
@@ -925,34 +931,34 @@ describe("HelmSecureIngress", () => {
925
931
 
926
932
  test("custom clusterIssuer flows through", () => {
927
933
  const result = HelmSecureIngress({ name: "web", clusterIssuer: "letsencrypt-staging" });
928
- const vals = result.values as any;
934
+ const vals = p(result.values);
929
935
  expect(vals.certManager.clusterIssuer).toBe("letsencrypt-staging");
930
936
  });
931
937
 
932
938
  test("custom ingressClassName flows through", () => {
933
939
  const result = HelmSecureIngress({ name: "web", ingressClassName: "nginx" });
934
- const vals = result.values as any;
940
+ const vals = p(result.values);
935
941
  expect(vals.ingress.className).toBe("nginx");
936
942
  });
937
943
 
938
944
  test("ingress uses Helm intrinsics", () => {
939
945
  const result = HelmSecureIngress({ name: "web" });
940
- expect(hasIntrinsic(result.ingress)).toBe(true);
946
+ expect(hasIntrinsic(p(result.ingress))).toBe(true);
941
947
  });
942
948
 
943
949
  test("certificate uses Helm intrinsics", () => {
944
950
  const result = HelmSecureIngress({ name: "web" });
945
- expect(hasIntrinsic(result.certificate!)).toBe(true);
951
+ expect(hasIntrinsic(p(result.certificate!))).toBe(true);
946
952
  });
947
953
 
948
954
  test("ingress uses Range for hosts", () => {
949
955
  const result = HelmSecureIngress({ name: "web" });
950
- expect(hasIntrinsic(result.ingress)).toBe(true);
956
+ expect(hasIntrinsic(p(result.ingress))).toBe(true);
951
957
  });
952
958
 
953
959
  test("default host includes chart name", () => {
954
960
  const result = HelmSecureIngress({ name: "web" });
955
- const vals = result.values as any;
961
+ const vals = p(result.values);
956
962
  expect(vals.ingress.hosts[0].host).toBe("web.example.com");
957
963
  });
958
964
  });
@@ -967,8 +973,8 @@ describe("HelmNamespaceEnv", () => {
967
973
 
968
974
  test("chart has correct metadata", () => {
969
975
  const result = HelmNamespaceEnv({ name: "dev" });
970
- expect(result.chart.name).toBe("dev");
971
- expect(result.chart.type).toBe("application");
976
+ expect(p(result.chart).name).toBe("dev");
977
+ expect(p(result.chart).type).toBe("application");
972
978
  });
973
979
 
974
980
  test("includes all governance resources by default", () => {
@@ -981,21 +987,21 @@ describe("HelmNamespaceEnv", () => {
981
987
  test("can exclude resourceQuota", () => {
982
988
  const result = HelmNamespaceEnv({ name: "dev", resourceQuota: false });
983
989
  expect(result.resourceQuota).toBeUndefined();
984
- const vals = result.values as any;
990
+ const vals = p(result.values);
985
991
  expect(vals.resourceQuota).toBeUndefined();
986
992
  });
987
993
 
988
994
  test("can exclude limitRange", () => {
989
995
  const result = HelmNamespaceEnv({ name: "dev", limitRange: false });
990
996
  expect(result.limitRange).toBeUndefined();
991
- const vals = result.values as any;
997
+ const vals = p(result.values);
992
998
  expect(vals.limitRange).toBeUndefined();
993
999
  });
994
1000
 
995
1001
  test("can exclude networkPolicy", () => {
996
1002
  const result = HelmNamespaceEnv({ name: "dev", networkPolicy: false });
997
1003
  expect(result.networkPolicy).toBeUndefined();
998
- const vals = result.values as any;
1004
+ const vals = p(result.values);
999
1005
  expect(vals.networkPolicy).toBeUndefined();
1000
1006
  });
1001
1007
 
@@ -1013,7 +1019,7 @@ describe("HelmNamespaceEnv", () => {
1013
1019
 
1014
1020
  test("default resourceQuota values", () => {
1015
1021
  const result = HelmNamespaceEnv({ name: "dev" });
1016
- const vals = result.values as any;
1022
+ const vals = p(result.values);
1017
1023
  expect(vals.resourceQuota.enabled).toBe(true);
1018
1024
  expect(vals.resourceQuota.hard.cpu).toBe("10");
1019
1025
  expect(vals.resourceQuota.hard.memory).toBe("20Gi");
@@ -1022,7 +1028,7 @@ describe("HelmNamespaceEnv", () => {
1022
1028
 
1023
1029
  test("default limitRange values", () => {
1024
1030
  const result = HelmNamespaceEnv({ name: "dev" });
1025
- const vals = result.values as any;
1031
+ const vals = p(result.values);
1026
1032
  expect(vals.limitRange.enabled).toBe(true);
1027
1033
  expect(vals.limitRange.default.cpu).toBe("500m");
1028
1034
  expect(vals.limitRange.defaultRequest.cpu).toBe("100m");
@@ -1030,7 +1036,7 @@ describe("HelmNamespaceEnv", () => {
1030
1036
 
1031
1037
  test("default networkPolicy values", () => {
1032
1038
  const result = HelmNamespaceEnv({ name: "dev" });
1033
- const vals = result.values as any;
1039
+ const vals = p(result.values);
1034
1040
  expect(vals.networkPolicy.enabled).toBe(true);
1035
1041
  expect(vals.networkPolicy.denyIngress).toBe(true);
1036
1042
  expect(vals.networkPolicy.denyEgress).toBe(false);
@@ -1038,13 +1044,13 @@ describe("HelmNamespaceEnv", () => {
1038
1044
 
1039
1045
  test("namespace uses Helm intrinsics", () => {
1040
1046
  const result = HelmNamespaceEnv({ name: "dev" });
1041
- expect(hasIntrinsic(result.namespace)).toBe(true);
1047
+ expect(hasIntrinsic(p(result.namespace))).toBe(true);
1042
1048
  });
1043
1049
 
1044
1050
  test("governance resources use If() conditional", () => {
1045
1051
  const result = HelmNamespaceEnv({ name: "dev" });
1046
- expect(hasIntrinsic(result.resourceQuota!)).toBe(true);
1047
- expect(hasIntrinsic(result.limitRange!)).toBe(true);
1048
- expect(hasIntrinsic(result.networkPolicy!)).toBe(true);
1052
+ expect(hasIntrinsic(p(result.resourceQuota!))).toBe(true);
1053
+ expect(hasIntrinsic(p(result.limitRange!))).toBe(true);
1054
+ expect(hasIntrinsic(p(result.networkPolicy!))).toBe(true);
1049
1055
  });
1050
1056
  });