@intentius/chant-lexicon-helm 0.124.31 → 0.124.33

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 (111) hide show
  1. package/dist/archive-render.js +76 -0
  2. package/dist/codegen/docs-cli.js +3 -0
  3. package/dist/codegen/docs.js +113 -0
  4. package/dist/codegen/generate-cli.js +19 -0
  5. package/dist/codegen/generate.js +210 -0
  6. package/dist/codegen/naming.js +29 -0
  7. package/dist/codegen/package.js +44 -0
  8. package/dist/codegen/values-type.js +87 -0
  9. package/dist/commands.js +413 -0
  10. package/dist/components/builders.js +9 -0
  11. package/dist/components/capability-plugin.js +17 -0
  12. package/dist/components/helm-upgrade.js +109 -0
  13. package/dist/components/index.js +11 -0
  14. package/dist/composites/catalog.js +1119 -0
  15. package/dist/composites/helm-batch-job.js +148 -0
  16. package/dist/composites/helm-crd-lifecycle.js +145 -0
  17. package/dist/composites/helm-cron-job.js +130 -0
  18. package/dist/composites/helm-daemon-set.js +125 -0
  19. package/dist/composites/helm-external-secret.js +63 -0
  20. package/dist/composites/helm-library.js +24 -0
  21. package/dist/composites/helm-microservice.js +260 -0
  22. package/dist/composites/helm-monitored-service.js +188 -0
  23. package/dist/composites/helm-namespace-env.js +116 -0
  24. package/dist/composites/helm-secure-ingress.js +86 -0
  25. package/dist/composites/helm-stateful-service.js +160 -0
  26. package/dist/composites/helm-web-app.js +201 -0
  27. package/dist/composites/helm-worker.js +164 -0
  28. package/dist/composites/index.js +13 -0
  29. package/dist/config.js +161 -0
  30. package/dist/coverage.js +37 -0
  31. package/dist/deep-observe-hooks.js +19 -0
  32. package/dist/deep-observe.js +55 -0
  33. package/dist/describe-resources.js +119 -0
  34. package/dist/describe-stack-status.js +66 -0
  35. package/dist/detect.js +11 -0
  36. package/dist/generated/index.js +11 -0
  37. package/dist/helpers.js +84 -0
  38. package/dist/import/generator.js +292 -0
  39. package/dist/import/parser.js +141 -0
  40. package/dist/import/template-stripper.js +86 -0
  41. package/dist/index.js +55 -0
  42. package/dist/integrity.json +2 -2
  43. package/dist/intrinsics.js +482 -0
  44. package/dist/lint/audit-catalog.js +33 -0
  45. package/dist/lint/audit-lineage.js +53 -0
  46. package/dist/lint/post-synth/helm-helpers.js +60 -0
  47. package/dist/lint/post-synth/index.js +48 -0
  48. package/dist/lint/post-synth/whm005-no-empty-wrapper.js +47 -0
  49. package/dist/lint/post-synth/whm101.js +38 -0
  50. package/dist/lint/post-synth/whm102.js +26 -0
  51. package/dist/lint/post-synth/whm103.js +51 -0
  52. package/dist/lint/post-synth/whm104.js +29 -0
  53. package/dist/lint/post-synth/whm105.js +23 -0
  54. package/dist/lint/post-synth/whm201.js +29 -0
  55. package/dist/lint/post-synth/whm202.js +42 -0
  56. package/dist/lint/post-synth/whm203.js +32 -0
  57. package/dist/lint/post-synth/whm204.js +51 -0
  58. package/dist/lint/post-synth/whm301.js +31 -0
  59. package/dist/lint/post-synth/whm302.js +33 -0
  60. package/dist/lint/post-synth/whm401.js +52 -0
  61. package/dist/lint/post-synth/whm402.js +38 -0
  62. package/dist/lint/post-synth/whm403.js +38 -0
  63. package/dist/lint/post-synth/whm404.js +30 -0
  64. package/dist/lint/post-synth/whm405.js +47 -0
  65. package/dist/lint/post-synth/whm406.js +27 -0
  66. package/dist/lint/post-synth/whm407.js +72 -0
  67. package/dist/lint/post-synth/whm501.js +89 -0
  68. package/dist/lint/post-synth/whm502.js +85 -0
  69. package/dist/lint/post-synth/whm503.js +82 -0
  70. package/dist/lint/post-synth/whm504.js +47 -0
  71. package/dist/lint/rules/chart-metadata.js +53 -0
  72. package/dist/lint/rules/no-hardcoded-image.js +50 -0
  73. package/dist/lint/rules/values-no-helm-tpl.js +78 -0
  74. package/dist/lint/rules/values-no-secrets.js +65 -0
  75. package/dist/list-artifacts.js +134 -0
  76. package/dist/lsp/completions.d.ts.map +1 -1
  77. package/dist/lsp/completions.js +18 -0
  78. package/dist/lsp/hover.d.ts.map +1 -1
  79. package/dist/lsp/hover.js +40 -0
  80. package/dist/manifest.json +1 -1
  81. package/dist/op/activities/cluster-probe.js +116 -0
  82. package/dist/op/activities/helm.js +449 -0
  83. package/dist/op/activities/index.js +10 -0
  84. package/dist/op/builders.js +45 -0
  85. package/dist/package-cli.js +21 -0
  86. package/dist/pinnability/actions.js +207 -0
  87. package/dist/pinnability/classify.js +416 -0
  88. package/dist/pinnability/conditions.js +0 -0
  89. package/dist/pinnability/index.js +16 -0
  90. package/dist/pinnability/localize.js +518 -0
  91. package/dist/pinnability/render-stream.js +53 -0
  92. package/dist/pinnability/values.js +151 -0
  93. package/dist/plugin.js +458 -0
  94. package/dist/project-codegen.js +215 -0
  95. package/dist/release-observe.js +287 -0
  96. package/dist/render-diff.js +279 -0
  97. package/dist/render-digest.js +0 -0
  98. package/dist/render-records.js +29 -0
  99. package/dist/render-store.js +401 -0
  100. package/dist/render-wrapper.js +164 -0
  101. package/dist/render.js +394 -0
  102. package/dist/resources.js +109 -0
  103. package/dist/serializer.js +903 -0
  104. package/dist/validate-cli.js +17 -0
  105. package/dist/validate.js +30 -0
  106. package/dist/values-probe.js +504 -0
  107. package/dist/wrapper-chart.js +95 -0
  108. package/package.json +11 -11
  109. package/src/codegen/package.ts +2 -2
  110. package/src/lsp/completions.ts +2 -1
  111. package/src/lsp/hover.ts +2 -1
package/dist/render.js ADDED
@@ -0,0 +1,394 @@
1
+ /**
2
+ * HelmRender — render an upstream Helm chart at chant build time.
3
+ *
4
+ * Most chant projects that want to install third-party operators (ESO,
5
+ * cert-manager, ingress-nginx, etc.) ran `helm template` or `helm install`
6
+ * as a separate deploy phase. That meant the chant build output was
7
+ * incomplete — `kubectl apply -f dist/...yaml` didn't carry those operators.
8
+ *
9
+ * `HelmRender({ repo, chart, version, values })` resolves at synth time:
10
+ * 1. Shells out to `helm template` (requires the `helm` binary in PATH).
11
+ * 2. Parses the resulting multi-document YAML.
12
+ * 3. Emits each rendered K8s manifest as a Declarable in the build output.
13
+ * 4. Caches the rendered output under `~/.chant/helm-renders/<hash>/`
14
+ * keyed by (repo, chart, version, values) so subsequent builds skip
15
+ * network access.
16
+ *
17
+ * The lexicon must include both `helm` and `k8s` (since rendered manifests
18
+ * are k8s resources).
19
+ *
20
+ * @example
21
+ * import { HelmRender } from "@intentius/chant-lexicon-helm";
22
+ *
23
+ * export const eso = HelmRender({
24
+ * name: "external-secrets",
25
+ * repo: "https://charts.external-secrets.io",
26
+ * chart: "external-secrets",
27
+ * version: "0.10.4",
28
+ * namespace: "external-secrets",
29
+ * createNamespace: true,
30
+ * values: { installCRDs: true },
31
+ * });
32
+ */
33
+ import { execFileSync } from "node:child_process";
34
+ import { createHash } from "node:crypto";
35
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
36
+ import { tmpdir } from "node:os";
37
+ import { join } from "node:path";
38
+ import { Composite } from "@intentius/chant";
39
+ // Use the k8s lexicon's Deployment as a generic Declarable wrapper for
40
+ // arbitrary K8s manifests. The k8s serializer reads props.apiVersion and
41
+ // props.kind verbatim when set, so the actual class doesn't matter.
42
+ import { Deployment } from "@intentius/chant-lexicon-k8s/generated/index";
43
+ import { applyRenderedPatches } from "@intentius/chant-lexicon-k8s/patch/rendered-patch";
44
+ import yaml from "js-yaml";
45
+ import { resolveCapabilityProfile } from "./config.js";
46
+ import { helmContentDigest, helmInputDigest } from "./render-digest.js";
47
+ import { findRenderByCacheKey, loadRenderContent, persistHelmRender, renderCacheKey, renderStoreRoot, } from "./render-store.js";
48
+ import { probeCoalescedValues } from "./values-probe.js";
49
+ import { recordHelmRender } from "./render-records.js";
50
+ // The invocation-record ledger lives in ./render-records.ts — a module with
51
+ // no runtime imports — so WHM503 (edge-imported via the post-synth barrel)
52
+ // can read it without pulling this module's heavy static graph. Re-exported
53
+ // here to keep the lexicon's public surface unchanged.
54
+ export { getHelmRenderRecords, clearHelmRenderRecords } from "./render-records.js";
55
+ /**
56
+ * Root of the legacy unpinned cache — the same directory (and the same
57
+ * `CHANT_HELM_RENDER_ROOT` override) as the content-addressed render store
58
+ * (#2035). Before this the unprofiled path hardcoded `~/.chant/helm-renders`
59
+ * and honoured no environment variable, so a hermetic or air-gapped build
60
+ * had no way to hand it a pre-rendered chart short of pre-warming `$HOME`.
61
+ * A function, not a module-level constant, so the override is read when a
62
+ * render happens rather than when this module loaded.
63
+ */
64
+ function cacheRoot() {
65
+ return renderStoreRoot();
66
+ }
67
+ /**
68
+ * Legacy cache key — truncated, unprefixed, input-derived. Since #1238 this
69
+ * keys only *unpinned* renders (and names the values tempfile): pinned
70
+ * renders live in the content-addressed store (./render-store.ts) under
71
+ * their full `sha256:` contentDigest, with the inputs index providing cache
72
+ * hits. Existing truncated-hash entries stay where they are — a new root,
73
+ * not a migration.
74
+ */
75
+ function cacheKey(props, profile) {
76
+ const stable = JSON.stringify({
77
+ repo: props.repo,
78
+ chart: props.chart,
79
+ version: props.version,
80
+ namespace: props.namespace ?? null,
81
+ values: props.values ?? null,
82
+ // Only present for pinned renders (which no longer read this cache but
83
+ // still name their values tempfile with this key). A pinned render must
84
+ // never reuse an unpinned render's bytes (or another profile's) — the
85
+ // profile is a real render input (#1235).
86
+ ...(profile
87
+ ? {
88
+ capabilityProfile: {
89
+ name: profile.name,
90
+ kubeVersion: profile.kubeVersion,
91
+ apiVersions: profile.apiVersions ?? [],
92
+ },
93
+ }
94
+ : {}),
95
+ });
96
+ return createHash("sha256").update(stable).digest("hex").slice(0, 16);
97
+ }
98
+ /**
99
+ * Version of the local helm binary, for the persisted `RenderManifest`.
100
+ * Load-bearing there — the defaulted kube version is a property of the
101
+ * binary, so this field explains a digest mismatch between machines. Never
102
+ * fails a build: `"unknown"` when helm cannot answer.
103
+ */
104
+ function helmBinaryVersion() {
105
+ try {
106
+ const out = execFileSync("helm", ["version", "--template", "{{.Version}}"], {
107
+ encoding: "utf8",
108
+ stdio: ["ignore", "pipe", "ignore"],
109
+ });
110
+ return out.trim().split("\n")[0] || "unknown";
111
+ }
112
+ catch {
113
+ return "unknown";
114
+ }
115
+ }
116
+ function renderViaHelm(props, profile) {
117
+ // Write values overrides to a tempfile if any.
118
+ let valuesArgs = [];
119
+ if (props.values && Object.keys(props.values).length > 0) {
120
+ const valuesPath = join(tmpdir(), `chant-helm-values-${cacheKey(props, profile)}.yaml`);
121
+ writeFileSync(valuesPath, yaml.dump(props.values));
122
+ valuesArgs = ["--values", valuesPath];
123
+ }
124
+ // When `repo` is set, helm fetches the chart by name+version from the repo.
125
+ // When `repo` is absent, treat `chart` as a local path.
126
+ const fetchArgs = [];
127
+ if (props.repo) {
128
+ fetchArgs.push("--repo", props.repo);
129
+ if (props.version)
130
+ fetchArgs.push("--version", props.version);
131
+ }
132
+ // When --repo is set, isolate helm's repository config + cache to a
133
+ // chant-private directory. Without this, helm tries to refresh ALL of the
134
+ // user's existing repo indexes (eks, jetstack, etc.) and fails with
135
+ // "no cached repo found" if any one has gone stale — even though we
136
+ // only care about the single repo the consumer asked for.
137
+ const isolationArgs = props.repo
138
+ ? [
139
+ "--repository-config",
140
+ "/dev/null",
141
+ "--repository-cache",
142
+ join(cacheRoot(), "_helm-repo-cache"),
143
+ ]
144
+ : [];
145
+ const args = [
146
+ "template",
147
+ props.name,
148
+ props.chart,
149
+ // Without this, `helm template` silently drops manifests shipped in the
150
+ // chart's (or a subchart's) crds/ directory.
151
+ "--include-crds",
152
+ // Pin .Capabilities to the declared cluster profile. Without these, the
153
+ // kube version defaults to one baked into the helm binary and APIVersions
154
+ // is empty — both silently, both making the rendered bytes a function of
155
+ // the local toolchain (#1235).
156
+ ...(profile
157
+ ? [
158
+ "--kube-version",
159
+ profile.kubeVersion,
160
+ ...(profile.apiVersions ?? []).flatMap((v) => ["--api-versions", v]),
161
+ ]
162
+ : []),
163
+ ...fetchArgs,
164
+ ...isolationArgs,
165
+ ...(props.namespace ? ["--namespace", props.namespace] : []),
166
+ ...valuesArgs,
167
+ ];
168
+ try {
169
+ const out = execFileSync("helm", args, {
170
+ encoding: "utf8",
171
+ stdio: ["ignore", "pipe", "pipe"],
172
+ maxBuffer: 16 * 1024 * 1024,
173
+ });
174
+ return out;
175
+ }
176
+ catch (err) {
177
+ const stderr = err && typeof err === "object" && "stderr" in err
178
+ ? String(err.stderr)
179
+ : String(err);
180
+ throw new Error(`HelmRender failed for ${props.repo}/${props.chart}@${props.version}:\n${stderr}\n` +
181
+ `Hint: ensure the 'helm' CLI is on PATH (helm version) and the chart is reachable.`);
182
+ }
183
+ }
184
+ function loadOrRender(props, profile) {
185
+ if (props.noCache) {
186
+ return renderViaHelm(props, profile);
187
+ }
188
+ const cacheDir = join(cacheRoot(), cacheKey(props, profile));
189
+ const cachePath = join(cacheDir, "manifests.yaml");
190
+ if (existsSync(cachePath)) {
191
+ return readFileSync(cachePath, "utf8");
192
+ }
193
+ const out = renderViaHelm(props, profile);
194
+ try {
195
+ mkdirSync(cacheDir, { recursive: true });
196
+ writeFileSync(cachePath, out);
197
+ }
198
+ catch {
199
+ // Cache write failure is non-fatal — the render is still in memory.
200
+ }
201
+ return out;
202
+ }
203
+ /**
204
+ * Best-effort coalesced-values probe (#1251) for a `HelmRender` invocation.
205
+ *
206
+ * The probe (`./values-probe.ts`) needs the chart source on disk — the same
207
+ * requirement the pinnability classifier (#1234) has — so it only runs for
208
+ * a local chart (`props.repo` unset, `props.chart` a directory with a
209
+ * `Chart.yaml`). A repo-fetched chart's source is never kept on disk here,
210
+ * so it gets no probe, the same v1 gate `render.ts`'s digest recording
211
+ * already documents.
212
+ *
213
+ * Never fatal: a probe failure (a chart the probe's template injection
214
+ * can't render, `helm` briefly unavailable, ...) must not break the actual
215
+ * render it is only observing. `undefined` means "no probe result" — never
216
+ * fabricated, and WHM504 simply has nothing to report for this render.
217
+ */
218
+ function runValuesProbeBestEffort(props) {
219
+ if (props.repo)
220
+ return undefined;
221
+ if (!existsSync(join(props.chart, "Chart.yaml")))
222
+ return undefined;
223
+ try {
224
+ return probeCoalescedValues({
225
+ chartDir: props.chart,
226
+ name: props.name,
227
+ releaseName: props.name,
228
+ namespace: props.namespace,
229
+ supplied: props.values && Object.keys(props.values).length > 0
230
+ ? [{ origin: "supplied file", values: props.values }]
231
+ : [],
232
+ });
233
+ }
234
+ catch {
235
+ // Non-fatal — see the docstring above. The main render already ran (or
236
+ // is about to) via its own `helm template` invocation; this extra,
237
+ // probe-only render observing the same chart is never load-bearing.
238
+ return undefined;
239
+ }
240
+ }
241
+ /**
242
+ * The pinned render path (#1238): the content-addressed store is both the
243
+ * cache and the durable record.
244
+ *
245
+ * Read: unless `noCache` (or `persist: false`) is set, the full-inputs
246
+ * cache key resolves through the store's inputs index to canonical bytes a
247
+ * previous identical render stored — semantically identical to helm's
248
+ * output (canonicalization only normalizes render noise) and byte-stable
249
+ * under re-canonicalization, so digests recompute identically.
250
+ *
251
+ * Write: a fresh render persists { canonical bytes, RenderManifest } unless
252
+ * the caller opted out — the manifest is written alongside every pinned
253
+ * render by default. `persist: true` with `noCache` still persists (a
254
+ * forced-fresh render is still a durable artifact); a persist failure on
255
+ * the default path is non-fatal like a legacy cache-write failure, but an
256
+ * explicit `persist: true` surfaces it.
257
+ */
258
+ function loadOrRenderPinned(props, profile, valuesProbe) {
259
+ const chartRef = props.repo ? `${props.repo}/${props.chart}` : props.chart;
260
+ const storeRead = !props.noCache && props.persist !== false;
261
+ const storeWrite = props.persist === true || (!props.noCache && props.persist !== false);
262
+ const key = renderCacheKey({
263
+ chart: chartRef,
264
+ chartVersion: props.version,
265
+ releaseName: props.name,
266
+ namespace: props.namespace,
267
+ values: props.values,
268
+ capabilityProfile: profile,
269
+ });
270
+ if (storeRead) {
271
+ const hit = findRenderByCacheKey(key);
272
+ if (hit) {
273
+ const content = loadRenderContent(hit.contentDigest);
274
+ if (content !== undefined)
275
+ return content;
276
+ // Index points at pruned/corrupt content — fall through and re-render.
277
+ }
278
+ }
279
+ const out = renderViaHelm(props, profile);
280
+ if (storeWrite) {
281
+ try {
282
+ persistHelmRender({
283
+ rendered: out,
284
+ releaseName: props.name,
285
+ chart: props.chart,
286
+ repo: props.repo || undefined,
287
+ chartVersion: props.version,
288
+ namespace: props.namespace,
289
+ values: props.values,
290
+ capabilityProfile: profile,
291
+ helmVersion: helmBinaryVersion(),
292
+ sourceRef: props.sourceRef,
293
+ coalescedValuesDigest: valuesProbe?.digest,
294
+ valueSources: valuesProbe?.valueSources,
295
+ });
296
+ }
297
+ catch (err) {
298
+ if (props.persist === true)
299
+ throw err;
300
+ // Default-on persistence degrades like a legacy cache-write failure:
301
+ // the render is still in memory and the build goes on.
302
+ }
303
+ }
304
+ return out;
305
+ }
306
+ function parseMultiDoc(text) {
307
+ const docs = yaml.loadAll(text);
308
+ return docs
309
+ .filter((d) => d !== null && typeof d === "object")
310
+ .filter((d) => d.kind && d.apiVersion);
311
+ }
312
+ /**
313
+ * Sanitize an arbitrary string into a valid TS/JS identifier suffix.
314
+ * Used to derive Composite Members keys from manifest kind+name pairs.
315
+ */
316
+ function safeKey(input) {
317
+ return input.replace(/[^a-zA-Z0-9_]/g, "_");
318
+ }
319
+ export const HelmRender = Composite((props) => {
320
+ // Resolve first: a named profile the config does not declare must fail the
321
+ // build here, before any helm invocation could silently render against the
322
+ // binary's default capabilities.
323
+ const profile = props.capabilityProfile !== undefined ? resolveCapabilityProfile(props.capabilityProfile) : undefined;
324
+ if (!profile && props.persist === true) {
325
+ throw new Error(`HelmRender "${props.name}": persist requires a pinned render, and this one is unpinned ` +
326
+ `(no capabilityProfile declared). An unpinned render's bytes are a function of the local ` +
327
+ `helm binary's defaulted capabilities, so they have no stable content identity to store ` +
328
+ `under. Declare capabilityProfile to pin the render (#1235), or drop persist.`);
329
+ }
330
+ // Build-time coalesced-values probe (#1251/#1252). Runs (best-effort) for
331
+ // a pinned render of a local chart — the same gate the digests below use,
332
+ // since the probe needs the chart source on disk and only a pinned render
333
+ // has a RenderManifest to carry its digest. `probeCoalescedValues` records
334
+ // itself into the WHM504 surface (`getValuesProbeRecords`) as part of
335
+ // running — no separate `recordValuesProbe` call needed here, and any
336
+ // dead assignment it found is worth reporting whether or not this render
337
+ // ultimately persists.
338
+ const valuesProbe = profile ? runValuesProbeBestEffort(props) : undefined;
339
+ const yamlText = profile ? loadOrRenderPinned(props, profile, valuesProbe) : loadOrRender(props, undefined);
340
+ const docs = applyRenderedPatches(parseMultiDoc(yamlText), props.patches, `HelmRender "${props.name}" (${props.chart}${props.version ? `@${props.version}` : ""})`);
341
+ // Digests are recorded only for pinned renders (#1237). Profile presence
342
+ // is the v1 gate: the classifier (#1234) needs the chart source on disk,
343
+ // which a repo-fetched render does not keep, so the gate here is the
344
+ // declared-inputs property, not a template analysis. An unpinned render's
345
+ // digest would be a function of the local helm binary — meaningless as an
346
+ // identity — so unpinned renders record neither digest.
347
+ const digests = profile
348
+ ? {
349
+ inputDigest: helmInputDigest({
350
+ // Same chart-reference convention helmInstall digests: a local
351
+ // path stays itself; a repo-fetched chart is `<repo-url>/<chart>`.
352
+ chart: props.repo ? `${props.repo}/${props.chart}` : props.chart,
353
+ chartVersion: props.version,
354
+ values: props.values ?? {},
355
+ capabilityProfile: {
356
+ kubeVersion: profile.kubeVersion,
357
+ apiVersions: profile.apiVersions,
358
+ },
359
+ }),
360
+ contentDigest: helmContentDigest(yamlText),
361
+ ...(valuesProbe ? { coalescedValuesDigest: valuesProbe.digest } : {}),
362
+ }
363
+ : {};
364
+ recordHelmRender({
365
+ name: props.name,
366
+ chart: props.chart,
367
+ version: props.version,
368
+ capabilityProfile: profile,
369
+ ...digests,
370
+ });
371
+ const out = {};
372
+ if (props.createNamespace && props.namespace) {
373
+ out["__namespace"] = new Deployment({
374
+ apiVersion: "v1",
375
+ kind: "Namespace",
376
+ metadata: { name: props.namespace },
377
+ });
378
+ }
379
+ const usedKeys = new Set();
380
+ for (let i = 0; i < docs.length; i++) {
381
+ const doc = docs[i];
382
+ const kind = doc.kind ?? "Unknown";
383
+ const name = doc.metadata?.name ?? `doc${i}`;
384
+ let key = safeKey(`${kind}_${name}`);
385
+ // Disambiguate on collision (e.g. same kind+name across docs).
386
+ let collisionN = 2;
387
+ while (usedKeys.has(key)) {
388
+ key = `${safeKey(`${kind}_${name}`)}_${collisionN++}`;
389
+ }
390
+ usedKeys.add(key);
391
+ out[key] = new Deployment(doc);
392
+ }
393
+ return out;
394
+ }, "HelmRender");
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Helm-specific resource and property types.
3
+ *
4
+ * Created via createResource/createProperty from @intentius/chant/runtime.
5
+ * These are static types (no upstream schema to fetch).
6
+ */
7
+ import { createResource, createProperty } from "@intentius/chant/runtime";
8
+ const LEXICON = "helm";
9
+ // ── Resources ─────────────────────────────────────────────
10
+ /**
11
+ * Helm::Chart — Chart.yaml metadata.
12
+ *
13
+ * Defines the chart's identity: apiVersion, name, version, appVersion,
14
+ * description, type (application|library), keywords, maintainers, dependencies.
15
+ */
16
+ export const Chart = createResource("Helm::Chart", LEXICON, {});
17
+ /**
18
+ * Helm::Values — Typed values definition.
19
+ *
20
+ * The props passed to the constructor become the default values emitted
21
+ * in values.yaml. The serializer also generates values.schema.json from
22
+ * the value types.
23
+ */
24
+ export const Values = createResource("Helm::Values", LEXICON, {});
25
+ /**
26
+ * Helm::Test — Helm test pod.
27
+ *
28
+ * Annotated with `helm.sh/hook: test` in the serialized output.
29
+ * Props should contain a K8s Pod spec.
30
+ */
31
+ export const HelmTest = createResource("Helm::Test", LEXICON, {});
32
+ /**
33
+ * Helm::Notes — NOTES.txt template content.
34
+ *
35
+ * The `content` prop is emitted as templates/NOTES.txt.
36
+ */
37
+ export const HelmNotes = createResource("Helm::Notes", LEXICON, {});
38
+ // ── Property types ────────────────────────────────────────
39
+ /**
40
+ * Helm::Hook — Lifecycle hook annotation.
41
+ *
42
+ * Wraps a K8s resource with helm.sh/hook annotations.
43
+ * Props: { hook: string, weight?: number, deletePolicy?: string, resource: Declarable }
44
+ */
45
+ export const HelmHook = createProperty("Helm::Hook", LEXICON);
46
+ /**
47
+ * Helm::Dependency — Chart dependency entry.
48
+ *
49
+ * Props: { name, version, repository, condition?, tags?, enabled?, importValues?, alias? }
50
+ */
51
+ export const HelmDependency = createProperty("Helm::Dependency", LEXICON);
52
+ /**
53
+ * Helm::Maintainer — Chart maintainer entry.
54
+ *
55
+ * Props: { name, email?, url? }
56
+ */
57
+ export const HelmMaintainer = createProperty("Helm::Maintainer", LEXICON);
58
+ /**
59
+ * Helm::ValuesOverride — Generate a named YAML override file.
60
+ *
61
+ * Emits a standalone YAML file inside the chart directory, intended to be
62
+ * passed as `helm upgrade -f chart-dir/values-base.yaml`. Use this for
63
+ * static configuration shared across all deployments (disabled bundled
64
+ * services, shared secret references, etc.).
65
+ *
66
+ * Props: { filename: string, values: Record<string, unknown> }
67
+ *
68
+ * ```ts
69
+ * new ValuesOverride({
70
+ * filename: "values-base",
71
+ * values: { postgresql: { install: false }, redis: { install: false } },
72
+ * })
73
+ * // → generates chart-dir/values-base.yaml
74
+ * ```
75
+ */
76
+ export const ValuesOverride = createResource("Helm::ValuesOverride", LEXICON, {});
77
+ // ── CRD ──────────────────────────────────────────────────
78
+ /**
79
+ * Helm::CRD — Custom Resource Definition placed in the crds/ directory.
80
+ *
81
+ * Props: { content: string, filename?: string }
82
+ */
83
+ export const HelmCRD = createResource("Helm::CRD", LEXICON, {});
84
+ // ── K8s resource types used by composites ────────────────
85
+ // These are thin wrappers so composite members are Declarable.
86
+ const K8S = "k8s";
87
+ export const Deployment = createResource("K8s::Apps::Deployment", K8S, {});
88
+ export const StatefulSet = createResource("K8s::Apps::StatefulSet", K8S, {});
89
+ export const DaemonSet = createResource("K8s::Apps::DaemonSet", K8S, {});
90
+ export const Service = createResource("K8s::Core::Service", K8S, {});
91
+ export const ServiceAccount = createResource("K8s::Core::ServiceAccount", K8S, {});
92
+ export const ConfigMap = createResource("K8s::Core::ConfigMap", K8S, {});
93
+ export const Namespace = createResource("K8s::Core::Namespace", K8S, {});
94
+ export const Job = createResource("K8s::Batch::Job", K8S, {});
95
+ export const CronJob = createResource("K8s::Batch::CronJob", K8S, {});
96
+ export const Ingress = createResource("K8s::Networking::Ingress", K8S, {});
97
+ export const NetworkPolicy = createResource("K8s::Networking::NetworkPolicy", K8S, {});
98
+ export const HPA = createResource("K8s::Autoscaling::HorizontalPodAutoscaler", K8S, {});
99
+ export const PDB = createResource("K8s::Policy::PodDisruptionBudget", K8S, {});
100
+ export const ResourceQuota = createResource("K8s::Core::ResourceQuota", K8S, {});
101
+ export const LimitRange = createResource("K8s::Core::LimitRange", K8S, {});
102
+ export const ClusterRole = createResource("K8s::Rbac::ClusterRole", K8S, {});
103
+ export const ClusterRoleBinding = createResource("K8s::Rbac::ClusterRoleBinding", K8S, {});
104
+ export const Role = createResource("K8s::Rbac::Role", K8S, {});
105
+ export const RoleBinding = createResource("K8s::Rbac::RoleBinding", K8S, {});
106
+ export const ExternalSecret = createResource("K8s::ExternalSecrets::ExternalSecret", K8S, {});
107
+ export const ServiceMonitor = createResource("K8s::Monitoring::ServiceMonitor", K8S, {});
108
+ export const PrometheusRule = createResource("K8s::Monitoring::PrometheusRule", K8S, {});
109
+ export const Certificate = createResource("K8s::CertManager::Certificate", K8S, {});