@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
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `addArchiveHelmRender` — fold a pinned render into a `BuildArchiveManifest`
3
+ * (#1240, epic #1228 Phase 3).
4
+ *
5
+ * #1238 gave a pinned render durable, content-addressed storage
6
+ * (./render-store.ts's `RenderManifest`, modeled on core's
7
+ * `BuildArchiveManifest`). This module is the seam between the two: it folds
8
+ * one render into a *build archive*'s manifest, the same self-contained
9
+ * bundle `docker-build`/`addArchiveTemplate` write into
10
+ * (packages/core/src/components/verbs/build-archive.ts), so a component that
11
+ * composes a `HelmRender` alongside an image/template gets one archive that
12
+ * carries all of it.
13
+ *
14
+ * A rendered chart is a template: it reuses `kind: "template"` rather than
15
+ * adding a new `BuildArchiveEntryKind`. What distinguishes a render from a
16
+ * synthesized CloudFormation/other IaC template is only its media type,
17
+ * `HELM_RENDER_MEDIA_TYPE` — and that is enough. Core's
18
+ * `lifecycle/oras-referrer-lookup.ts` classifies *referrers* (SBOM/
19
+ * provenance/signature) by the referrer's own media type, keyed only by the
20
+ * subject *digest* it discovers against — never by the subject entry's own
21
+ * `kind`/`mediaType`. A render's digest is therefore attachable exactly like
22
+ * an image's: `oras discover <repo>@<renderDigest>` finds the same
23
+ * referrers a signing/attestation pipeline would attach to any other
24
+ * artifact, with no change to that lookup needed (see
25
+ * ./archive-render.test.ts for the referrer-classification proof).
26
+ *
27
+ * Mirrors `addArchiveTemplate` (packages/core/src/components/verbs/build.ts)
28
+ * in every way that matters: a plain, pure function (not a registered
29
+ * capability — rendering happens at chant's existing build/synth time, not
30
+ * as a new deploy-time verb), accumulates onto a caller-supplied manifest so
31
+ * a component's whole build phase shares one archive, and threads an
32
+ * optional `sourceRef` into a `provenance` link (#614). It differs in one
33
+ * respect: rather than recomputing a digest from raw content bytes (core's
34
+ * `contentDigest`), it reuses the render's own `contentDigest` — the real
35
+ * `sha256:` over the canonical rendered stream that `helmContentDigest`
36
+ * already computed and `persistHelmRender` already wrote to the render
37
+ * store — so the archive entry's identity and the render store's identity
38
+ * are the same digest, not two independent hashes of the same bytes.
39
+ */
40
+ import { addArchiveEntry, createBuildArchiveManifest, } from "@intentius/chant/components/verbs/build-archive";
41
+ /**
42
+ * Media type for a pinned helm render folded into a build archive as a
43
+ * `template`-kind entry. `v1` names the canonical-stream shape
44
+ * `canonicalizeRender` (./render-digest.ts) produces — a versioned suffix so
45
+ * a future incompatible canonicalization can be told apart from this one
46
+ * without guessing from content alone.
47
+ */
48
+ export const HELM_RENDER_MEDIA_TYPE = "application/vnd.chant.helm-render.v1+yaml";
49
+ /**
50
+ * Fold one pinned render into a build archive's manifest as a
51
+ * `template`-kind entry with `mediaType: HELM_RENDER_MEDIA_TYPE` — the
52
+ * render peer of `addArchiveTemplate`. The entry's digest is the render's
53
+ * own `contentDigest` (real `sha256:` over the canonical rendered stream,
54
+ * ./render-digest.ts's `helmContentDigest`), so the same digest that keys
55
+ * the render store's `sha256-<hex>/` entry (./render-store.ts) also keys
56
+ * this archive entry — one identity, not a second hash of the same bytes.
57
+ * Adding the entry recomputes `manifest.manifestDigest`
58
+ * (`computeManifestDigest`), so the render rolls into the archive's own
59
+ * aggregate identity exactly as an image or template does.
60
+ */
61
+ export function addArchiveHelmRender(input) {
62
+ const digest = input.render.contentDigest;
63
+ const base = input.manifest ?? createBuildArchiveManifest("unknown");
64
+ const sourceRef = input.sourceRef ?? input.render.sourceRef ?? undefined;
65
+ const provenance = sourceRef
66
+ ? { sourceRef, artifactDigest: digest }
67
+ : undefined;
68
+ const manifest = addArchiveEntry(base, {
69
+ kind: "template",
70
+ path: input.path,
71
+ digest,
72
+ mediaType: HELM_RENDER_MEDIA_TYPE,
73
+ ...(provenance ? { provenance } : {}),
74
+ });
75
+ return { digest, manifest };
76
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env tsx
2
+ import { generateDocs } from "./docs.js";
3
+ await generateDocs({ verbose: true });
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Documentation generation for the Helm lexicon.
3
+ *
4
+ * Generates Starlight MDX pages for Helm entities using the core docs pipeline.
5
+ */
6
+ import { dirname, join } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
9
+ function serviceFromType(resourceType) {
10
+ const parts = resourceType.split("::");
11
+ return parts.length >= 2 ? parts[1] : "Helm";
12
+ }
13
+ const overview = `The **Helm** lexicon generates production-quality, parameterized Helm charts from TypeScript.
14
+
15
+ Unlike static manifest generators, the Helm lexicon produces real \`{{ .Values.x }}\` directives, \`values.yaml\`, \`values.schema.json\`, conditional resources, Helm hooks, and standard \`_helpers.tpl\`.
16
+
17
+ Install it with:
18
+
19
+ \`\`\`bash
20
+ npm install --save-dev @intentius/chant-lexicon-helm @intentius/chant-lexicon-k8s
21
+ \`\`\`
22
+
23
+ ## Quick Start
24
+
25
+ \`\`\`typescript
26
+ import { Chart, Values } from "@intentius/chant-lexicon-helm";
27
+ import { values, include, printf, toYaml } from "@intentius/chant-lexicon-helm";
28
+ import { Deployment, Service } from "@intentius/chant-lexicon-k8s";
29
+
30
+ export const chart = new Chart({
31
+ apiVersion: "v2",
32
+ name: "my-app",
33
+ version: "0.1.0",
34
+ type: "application",
35
+ });
36
+
37
+ export const valuesSchema = new Values({
38
+ replicaCount: 1,
39
+ image: { repository: "nginx", tag: "", pullPolicy: "IfNotPresent" },
40
+ service: { type: "ClusterIP", port: 80 },
41
+ });
42
+
43
+ export const deployment = new Deployment({
44
+ metadata: {
45
+ name: include("my-app.fullname"),
46
+ labels: include("my-app.labels"),
47
+ },
48
+ spec: {
49
+ replicas: values.replicaCount,
50
+ template: {
51
+ spec: {
52
+ containers: [{
53
+ name: "my-app",
54
+ image: printf("%s:%s", values.image.repository, values.image.tag),
55
+ ports: [{ containerPort: values.service.port, name: "http" }],
56
+ resources: toYaml(values.resources),
57
+ }],
58
+ },
59
+ },
60
+ },
61
+ });
62
+ \`\`\`
63
+
64
+ Build the chart:
65
+
66
+ \`\`\`bash
67
+ chant build
68
+ helm lint dist/
69
+ helm template test dist/
70
+ \`\`\`
71
+ `;
72
+ const outputFormat = `## Output structure
73
+
74
+ The Helm serializer produces a complete chart directory:
75
+
76
+ \`\`\`
77
+ dist/
78
+ Chart.yaml ← from Chart entity
79
+ values.yaml ← from Values entity (defaults)
80
+ values.schema.json ← auto-generated JSON Schema from Values types
81
+ .helmignore
82
+ templates/
83
+ _helpers.tpl ← auto-generated standard helpers
84
+ deployment.yaml ← K8s resources with {{ .Values.x }} directives
85
+ service.yaml
86
+ NOTES.txt ← from HelmNotes entity
87
+ tests/
88
+ test-connection.yaml ← from HelmTest entities
89
+ \`\`\`
90
+ `;
91
+ export async function generateDocs(opts) {
92
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
93
+ const config = {
94
+ name: "helm",
95
+ displayName: "Helm",
96
+ description: "Typed constructors for parameterized Helm charts",
97
+ distDir: join(pkgDir, "dist"),
98
+ outDir: join(pkgDir, "docs"),
99
+ basePath: process.env.DOCS_BASE_PATH ?? "/chant/lexicons/helm/",
100
+ overview,
101
+ outputFormat,
102
+ serviceFromType,
103
+ // `lint-rules` under docs/pages/ is helm's rules documentation; the
104
+ // generated `rules` table duplicated it, and helm's own declared rules
105
+ // page was being silently overwritten by that table (#1312).
106
+ suppressPages: ["pseudo-parameters"],
107
+ };
108
+ const result = docsPipeline(config);
109
+ writeDocsSite(config, result);
110
+ if (opts?.verbose) {
111
+ console.error(`Generated ${result.pages.size} documentation pages`);
112
+ }
113
+ }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI entry point for Helm lexicon generation.
4
+ */
5
+ import { dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import { generate, writeGeneratedFiles } from "./generate.js";
8
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
9
+ async function main() {
10
+ const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
11
+ console.error("Generating Helm lexicon (static types)...");
12
+ const result = await generate({ verbose });
13
+ writeGeneratedFiles(result, pkgDir);
14
+ console.error(`Generated ${result.resources} resources, ${result.properties} property types`);
15
+ }
16
+ main().catch((err) => {
17
+ console.error("Generation failed:", err);
18
+ process.exit(1);
19
+ });
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Helm generation pipeline — static type generation.
3
+ *
4
+ * Unlike AWS/K8s, Helm types are defined statically (no upstream schema to
5
+ * fetch). This module generates lexicon-helm.json, index.d.ts, and index.ts
6
+ * directly from the known Helm resource/property type definitions.
7
+ */
8
+ import { writeGeneratedArtifacts, } from "@intentius/chant/codegen/generate";
9
+ const HELM_TYPES = [
10
+ {
11
+ resourceType: "Helm::Chart",
12
+ exportName: "Chart",
13
+ kind: "resource",
14
+ description: "Chart.yaml metadata — defines the chart identity, version, and type.",
15
+ props: {
16
+ apiVersion: { type: "string", description: "Chart API version (v2)", required: true },
17
+ name: { type: "string", description: "Chart name", required: true },
18
+ version: { type: "string", description: "Chart version (SemVer)", required: true },
19
+ appVersion: { type: "string", description: "Version of the app deployed by this chart" },
20
+ description: { type: "string", description: "A single-sentence description of this chart" },
21
+ type: { type: "string", description: "Chart type: application or library" },
22
+ keywords: { type: "string[]", description: "Keywords for chart search" },
23
+ home: { type: "string", description: "URL of the project home page" },
24
+ icon: { type: "string", description: "URL to an SVG or PNG image for the chart" },
25
+ deprecated: { type: "boolean", description: "Whether this chart is deprecated" },
26
+ sources: { type: "string[]", description: "URLs to source code for this chart" },
27
+ maintainers: { type: "HelmMaintainerProps[]", description: "List of chart maintainers" },
28
+ annotations: { type: "Record<string, string>", description: "Arbitrary key-value annotations" },
29
+ kubeVersion: { type: "string", description: "SemVer range of compatible Kubernetes versions" },
30
+ condition: { type: "string", description: "YAML path for chart enablement (subcharts)" },
31
+ tags: { type: "string", description: "Tags for grouping charts for enabling/disabling" },
32
+ },
33
+ },
34
+ {
35
+ resourceType: "Helm::Values",
36
+ exportName: "Values",
37
+ kind: "resource",
38
+ description: "Typed values definition — emits values.yaml and values.schema.json.",
39
+ props: {},
40
+ },
41
+ {
42
+ resourceType: "Helm::Test",
43
+ exportName: "HelmTest",
44
+ kind: "resource",
45
+ description: "Helm test pod — annotated with helm.sh/hook: test.",
46
+ props: {
47
+ resource: { type: "object", description: "K8s Pod resource to use as the test" },
48
+ },
49
+ },
50
+ {
51
+ resourceType: "Helm::Notes",
52
+ exportName: "HelmNotes",
53
+ kind: "resource",
54
+ description: "NOTES.txt template content — displayed after helm install.",
55
+ props: {
56
+ content: { type: "string", description: "NOTES.txt content (may contain Go template expressions)", required: true },
57
+ },
58
+ },
59
+ {
60
+ resourceType: "Helm::Hook",
61
+ exportName: "HelmHook",
62
+ kind: "property",
63
+ description: "Lifecycle hook annotation — wraps a K8s resource with helm.sh/hook annotations.",
64
+ props: {
65
+ hook: { type: "string", description: "Hook type: pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete, pre-rollback, post-rollback, test", required: true },
66
+ weight: { type: "number", description: "Hook execution order weight" },
67
+ deletePolicy: { type: "string", description: "Hook delete policy: before-hook-creation, hook-succeeded, hook-failed" },
68
+ resource: { type: "object", description: "K8s resource to annotate with the hook", required: true },
69
+ },
70
+ },
71
+ {
72
+ resourceType: "Helm::Dependency",
73
+ exportName: "HelmDependency",
74
+ kind: "property",
75
+ description: "Chart dependency entry for Chart.yaml dependencies.",
76
+ props: {
77
+ name: { type: "string", description: "Dependency chart name", required: true },
78
+ version: { type: "string", description: "Dependency version range (SemVer)", required: true },
79
+ repository: { type: "string", description: "Repository URL", required: true },
80
+ condition: { type: "string", description: "YAML path that enables/disables this dependency" },
81
+ tags: { type: "string[]", description: "Tags for grouping dependencies" },
82
+ enabled: { type: "boolean", description: "Whether this dependency is enabled" },
83
+ importValues: { type: "unknown[]", description: "Values to import from dependency (import-values in Chart.yaml)" },
84
+ alias: { type: "string", description: "Alias for the dependency" },
85
+ },
86
+ },
87
+ {
88
+ resourceType: "Helm::Maintainer",
89
+ exportName: "HelmMaintainer",
90
+ kind: "property",
91
+ description: "Chart maintainer entry for Chart.yaml maintainers.",
92
+ props: {
93
+ name: { type: "string", description: "Maintainer name", required: true },
94
+ email: { type: "string", description: "Maintainer email" },
95
+ url: { type: "string", description: "Maintainer URL" },
96
+ },
97
+ },
98
+ {
99
+ resourceType: "Helm::CRD",
100
+ exportName: "HelmCRD",
101
+ kind: "resource",
102
+ description: "Custom Resource Definition — placed in the crds/ directory.",
103
+ props: {
104
+ content: { type: "string", description: "CRD YAML content", required: true },
105
+ filename: { type: "string", description: "CRD filename (e.g. mycrd.yaml)" },
106
+ },
107
+ },
108
+ ];
109
+ // ── Generate artifacts ──────────────────────────────────
110
+ function generateLexiconJSON() {
111
+ const registry = {};
112
+ for (const t of HELM_TYPES) {
113
+ registry[t.exportName] = {
114
+ resourceType: t.resourceType,
115
+ kind: t.kind,
116
+ description: t.description,
117
+ props: t.props,
118
+ };
119
+ }
120
+ return JSON.stringify(registry, null, 2);
121
+ }
122
+ function generateTypesDTS() {
123
+ const lines = [
124
+ '// Auto-generated Helm lexicon type declarations',
125
+ '// Do not edit manually',
126
+ '',
127
+ '/** Minimal Declarable interface for generated types. */',
128
+ 'interface Declarable { [key: string]: unknown; }',
129
+ '',
130
+ ];
131
+ for (const t of HELM_TYPES) {
132
+ const className = t.exportName;
133
+ const propsInterface = `${className}Props`;
134
+ // Generate props interface
135
+ lines.push(`/** ${t.description} */`);
136
+ lines.push(`export interface ${propsInterface} {`);
137
+ for (const [key, prop] of Object.entries(t.props)) {
138
+ const optional = prop.required ? "" : "?";
139
+ lines.push(` /** ${prop.description} */`);
140
+ lines.push(` ${key}${optional}: ${prop.type};`);
141
+ }
142
+ if (Object.keys(t.props).length === 0) {
143
+ lines.push(" [key: string]: unknown;");
144
+ }
145
+ lines.push("}");
146
+ lines.push("");
147
+ // Generate class declaration
148
+ lines.push(`/** ${t.description} */`);
149
+ lines.push(`export declare const ${className}: new (props: ${propsInterface}) => Declarable;`);
150
+ lines.push("");
151
+ }
152
+ return lines.join("\n");
153
+ }
154
+ function generateRuntimeIndex() {
155
+ const lines = [
156
+ '// Auto-generated Helm lexicon runtime index',
157
+ '// Do not edit manually',
158
+ '',
159
+ 'import { createResource, createProperty } from "@intentius/chant/runtime";',
160
+ '',
161
+ ];
162
+ for (const t of HELM_TYPES) {
163
+ if (t.kind === "resource") {
164
+ lines.push(`export const ${t.exportName} = createResource("${t.resourceType}", "helm", {});`);
165
+ }
166
+ else {
167
+ lines.push(`export const ${t.exportName} = createProperty("${t.resourceType}", "helm");`);
168
+ }
169
+ }
170
+ lines.push("");
171
+ return lines.join("\n");
172
+ }
173
+ /**
174
+ * Run the Helm generation pipeline.
175
+ * Since Helm types are static, this doesn't fetch anything.
176
+ */
177
+ export async function generate(opts = {}) {
178
+ const log = opts.verbose
179
+ ? (msg) => console.error(msg)
180
+ : (_msg) => { };
181
+ log("Generating Helm lexicon (static types)...");
182
+ const lexiconJSON = generateLexiconJSON();
183
+ const typesDTS = generateTypesDTS();
184
+ const indexTS = generateRuntimeIndex();
185
+ const resourceCount = HELM_TYPES.filter((t) => t.kind === "resource").length;
186
+ const propertyCount = HELM_TYPES.filter((t) => t.kind === "property").length;
187
+ log(`Generated ${resourceCount} resources, ${propertyCount} property types`);
188
+ return {
189
+ lexiconJSON,
190
+ typesDTS,
191
+ indexTS,
192
+ resources: resourceCount,
193
+ properties: propertyCount,
194
+ enums: 0,
195
+ warnings: [],
196
+ };
197
+ }
198
+ /**
199
+ * Write generated artifacts to disk.
200
+ */
201
+ export function writeGeneratedFiles(result, baseDir) {
202
+ writeGeneratedArtifacts({
203
+ baseDir,
204
+ files: {
205
+ "lexicon-helm.json": result.lexiconJSON,
206
+ "index.d.ts": result.typesDTS,
207
+ "index.ts": result.indexTS,
208
+ },
209
+ });
210
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Helm naming strategy — trivial mapping since Helm types are few and static.
3
+ *
4
+ * Maps Helm::{TypeName} → TypeName (e.g. Helm::Chart → Chart).
5
+ */
6
+ import { NamingStrategy as CoreNamingStrategy, } from "@intentius/chant/codegen/naming";
7
+ const helmNamingConfig = {
8
+ priorityNames: {
9
+ "Helm::Chart": "Chart",
10
+ "Helm::Values": "Values",
11
+ "Helm::Test": "HelmTest",
12
+ "Helm::Notes": "HelmNotes",
13
+ "Helm::Hook": "HelmHook",
14
+ "Helm::Dependency": "HelmDependency",
15
+ },
16
+ priorityAliases: {},
17
+ priorityPropertyAliases: {},
18
+ serviceAbbreviations: {},
19
+ shortName: (typeName) => typeName.split("::").pop() ?? typeName,
20
+ serviceName: (_typeName) => "Helm",
21
+ };
22
+ /**
23
+ * Helm naming strategy.
24
+ */
25
+ export class NamingStrategy extends CoreNamingStrategy {
26
+ constructor(inputs) {
27
+ super(inputs, helmNamingConfig);
28
+ }
29
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Helm lexicon packaging — delegates to core packagePipeline
3
+ * with Helm-specific manifest building and skill collection.
4
+ */
5
+ import { readFileSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { moduleSourceDir } from "@intentius/chant/module-source";
8
+ import { helmPlugin } from "../plugin.js";
9
+ import { packagePipeline, collectSkills, } from "@intentius/chant/codegen/package";
10
+ import { generate } from "./generate.js";
11
+ const pkgDir = dirname(moduleSourceDir(import.meta.url));
12
+ /**
13
+ * Package the Helm lexicon into a distributable BundleSpec.
14
+ */
15
+ export async function packageLexicon(opts = {}) {
16
+ const pkgJson = JSON.parse(readFileSync(join(pkgDir, "..", "package.json"), "utf-8"));
17
+ return packagePipeline({
18
+ generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
19
+ buildManifest: (_genResult) => {
20
+ return {
21
+ name: "helm",
22
+ version: pkgJson.version ?? "0.0.0",
23
+ chantVersion: ">=0.1.0",
24
+ namespace: "Helm",
25
+ // Derived from the plugin's own registration (chant #1067) — this
26
+ // used to be a hand-maintained array of 7 entries, independent of
27
+ // ../plugin.ts's real registration (which has grown to 22) and
28
+ // never updated to match. That drift is exactly why an
29
+ // independent second source of truth for the same data is
30
+ // dangerous: the generated intrinsics doc page (chant #1062) was
31
+ // silently reporting "7 intrinsic functions" for a lexicon that
32
+ // ships 22.
33
+ intrinsics: helmPlugin.intrinsics?.() ?? [],
34
+ pseudoParameters: {},
35
+ };
36
+ },
37
+ srcDir: pkgDir,
38
+ collectSkills: () => {
39
+ const skillDefs = helmPlugin.skills?.() ?? [];
40
+ return collectSkills(skillDefs);
41
+ },
42
+ version: pkgJson.version ?? "0.0.0",
43
+ }, opts);
44
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * A TypeScript type for a chart's values when the chart ships no
3
+ * `values.schema.json`, inferred from its `values.yaml` defaults.
4
+ *
5
+ * Every member is optional, since an override sets any subset. An object's
6
+ * keys are the ones `values.yaml` lists and no others, so a misspelled key
7
+ * fails to typecheck. A default that says nothing about its type becomes
8
+ * `unknown`: a `null` or empty value (`key:` or `key: ~`) is `unknown`, an
9
+ * empty object (`podAnnotations: {}`) a map of `unknown`, an empty list an
10
+ * array of `unknown`. An empty string is still a string. Keys are quoted
11
+ * when they are not identifiers and never renamed.
12
+ */
13
+ import { tsPropertyKey } from "@intentius/chant/codegen/json-schema-to-ts";
14
+ /** The type expression for a parsed `values.yaml` document. */
15
+ export function inferValuesType(values) {
16
+ if (values === null || values === undefined)
17
+ return "Record<string, unknown>";
18
+ return render(shape(values), "");
19
+ }
20
+ function shape(value) {
21
+ if (value === null || value === undefined)
22
+ return { kind: "unknown" };
23
+ if (typeof value === "string")
24
+ return { kind: "scalar", type: "string" };
25
+ if (typeof value === "number")
26
+ return { kind: "scalar", type: "number" };
27
+ if (typeof value === "boolean")
28
+ return { kind: "scalar", type: "boolean" };
29
+ if (value instanceof Date)
30
+ return { kind: "scalar", type: "string" };
31
+ if (Array.isArray(value)) {
32
+ if (value.length === 0)
33
+ return { kind: "array", element: { kind: "unknown" } };
34
+ return { kind: "array", element: value.map(shape).reduce(merge) };
35
+ }
36
+ if (typeof value === "object") {
37
+ const entries = Object.entries(value);
38
+ if (entries.length === 0)
39
+ return { kind: "map" };
40
+ return { kind: "object", members: new Map(entries.map(([k, v]) => [k, shape(v)])) };
41
+ }
42
+ return { kind: "unknown" };
43
+ }
44
+ /** Combine the shapes of two list elements: objects merge their keys, anything else unions. */
45
+ function merge(a, b) {
46
+ if (a.kind === "unknown" || b.kind === "unknown")
47
+ return { kind: "unknown" };
48
+ if (a.kind === "object" && b.kind === "object") {
49
+ const members = new Map(a.members);
50
+ for (const [k, s] of b.members)
51
+ members.set(k, members.has(k) ? merge(members.get(k), s) : s);
52
+ return { kind: "object", members };
53
+ }
54
+ if (a.kind === "scalar" && b.kind === "scalar" && a.type === b.type)
55
+ return a;
56
+ if (a.kind === "map" && b.kind === "map")
57
+ return a;
58
+ if (a.kind === "array" && b.kind === "array")
59
+ return { kind: "array", element: merge(a.element, b.element) };
60
+ const shapes = [...(a.kind === "union" ? a.shapes : [a]), ...(b.kind === "union" ? b.shapes : [b])];
61
+ return { kind: "union", shapes };
62
+ }
63
+ function render(s, indent) {
64
+ switch (s.kind) {
65
+ case "unknown":
66
+ return "unknown";
67
+ case "scalar":
68
+ return s.type;
69
+ case "map":
70
+ return "Record<string, unknown>";
71
+ case "array": {
72
+ const element = render(s.element, indent);
73
+ return /^[A-Za-z]+$/.test(element) ? `${element}[]` : `Array<${element}>`;
74
+ }
75
+ case "union":
76
+ return [...new Set(s.shapes.map((x) => render(x, indent)))].join(" | ");
77
+ case "object": {
78
+ const inner = indent + " ";
79
+ const lines = ["{"];
80
+ for (const [key, member] of s.members) {
81
+ lines.push(`${inner}${tsPropertyKey(key)}?: ${render(member, inner)};`);
82
+ }
83
+ lines.push(`${indent}}`);
84
+ return lines.join("\n");
85
+ }
86
+ }
87
+ }