-
Notifications
You must be signed in to change notification settings - Fork 26
docs: TypeScript charts specification #500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # TypeScript Charts for Nelm | ||
|
|
||
| Alternative to Go templates for generating Kubernetes manifests. | ||
|
|
||
| ## Documents | ||
|
|
||
| - [decisions.md](./decisions.md) — Accepted design decisions | ||
| - [api.md](./api.md) — HelmContext API and types | ||
| - [sdk.md](./sdk.md) — @nelm/sdk package structure | ||
| - [workflow.md](./workflow.md) — Development and deployment workflow | ||
| - [cli.md](./cli.md) — CLI commands | ||
|
|
||
| ## Overview | ||
|
|
||
| TypeScript charts provide a type-safe, scalable alternative to Go templates while maintaining Helm compatibility. | ||
|
|
||
| ```typescript | ||
| import { HelmContext, Manifest } from '@nelm/sdk' | ||
| import { Values } from './values.types' | ||
|
|
||
| export default function render(ctx: HelmContext<Values>): Manifest[] { | ||
| return [ | ||
| { | ||
| apiVersion: 'apps/v1', | ||
| kind: 'Deployment', | ||
| metadata: { | ||
| name: ctx.Release.Name, | ||
| namespace: ctx.Release.Namespace, | ||
| }, | ||
| spec: { | ||
| replicas: ctx.Values.replicas, | ||
| // ... | ||
| }, | ||
| }, | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ## Key Principles | ||
|
|
||
| 1. **Pure functions** — `render(ctx) → Manifest[]` | ||
| 2. **Explicit context** — everything via `ctx`, no globals | ||
| 3. **Isolation** — subcharts render independently | ||
| 4. **Type safety** — Values types generated from schema | ||
| 5. **No magic** — predictable, testable code |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,241 @@ | ||||||
| # HelmContext API | ||||||
|
|
||||||
| ## Main Interface | ||||||
|
|
||||||
| ```typescript | ||||||
| interface HelmContext<V = unknown> { | ||||||
| // Data | ||||||
| Values: V | ||||||
| Release: Release | ||||||
| Chart: Chart | ||||||
| Capabilities: Capabilities | ||||||
| Files: Files | ||||||
|
|
||||||
| // Functions (injected from Go) | ||||||
| lookup<T = unknown>(apiVersion: string, kind: string, namespace: string, name: string): T | null | ||||||
|
|
||||||
| // Serialization | ||||||
| toYaml(obj: unknown): string | ||||||
|
||||||
| fromYaml<T>(str: string): T | ||||||
| toJson(obj: unknown): string | ||||||
| fromJson<T>(str: string): T | ||||||
|
|
||||||
| // Encoding | ||||||
| b64encode(str: string): string | ||||||
| b64decode(str: string): string | ||||||
|
|
||||||
| // Hashing | ||||||
| sha256(str: string): string | ||||||
| sha1(str: string): string | ||||||
| md5(str: string): string | ||||||
|
|
||||||
| // String manipulation (Helm-compatible) | ||||||
| indent(str: string, spaces: number): string | ||||||
| nindent(str: string, spaces: number): string | ||||||
| trim(str: string): string | ||||||
| trimPrefix(str: string, prefix: string): string | ||||||
| trimSuffix(str: string, suffix: string): string | ||||||
| upper(str: string): string | ||||||
| lower(str: string): string | ||||||
| title(str: string): string | ||||||
| quote(str: string): string | ||||||
| squote(str: string): string | ||||||
|
|
||||||
| // ... other Helm helpers | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Release | ||||||
|
|
||||||
| ```typescript | ||||||
| interface Release { | ||||||
| Name: string | ||||||
| Namespace: string | ||||||
| IsUpgrade: boolean | ||||||
| IsInstall: boolean | ||||||
| Revision: number | ||||||
| Service: string // "Helm" or "Nelm" | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Chart | ||||||
|
|
||||||
| ```typescript | ||||||
| interface Chart { | ||||||
| Name: string | ||||||
| Version: string | ||||||
| AppVersion: string | ||||||
| Description: string | ||||||
| Keywords: string[] | ||||||
| Home: string | ||||||
| Sources: string[] | ||||||
| Icon: string | ||||||
| Deprecated: boolean | ||||||
| Type: string // "application" or "library" | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Capabilities | ||||||
|
|
||||||
| ```typescript | ||||||
| interface Capabilities { | ||||||
| KubeVersion: KubeVersion | ||||||
| APIVersions: APIVersions | ||||||
| HelmVersion: HelmVersion | ||||||
| } | ||||||
|
|
||||||
| interface KubeVersion { | ||||||
| Major: string | ||||||
| Minor: string | ||||||
| GitVersion: string // e.g., "v1.28.3" | ||||||
|
|
||||||
| // Semver comparison helpers | ||||||
| gte(version: string): boolean | ||||||
| gt(version: string): boolean | ||||||
| lte(version: string): boolean | ||||||
| lt(version: string): boolean | ||||||
| eq(version: string): boolean | ||||||
| } | ||||||
|
|
||||||
| interface APIVersions { | ||||||
| list: string[] | ||||||
| has(apiVersion: string): boolean | ||||||
| } | ||||||
|
|
||||||
| interface HelmVersion { | ||||||
| Version: string | ||||||
| GitCommit: string | ||||||
| GoVersion: string | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Files | ||||||
|
|
||||||
| ```typescript | ||||||
| interface Files { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make it We don't really need all these methods, the user can do it himself. And we have all the Data from files already at this point (Helm reads all of them into the memory, even if not used). |
||||||
| get(path: string): string | ||||||
| getBytes(path: string): Uint8Array | ||||||
| glob(pattern: string): Map<string, string> // path -> content | ||||||
| lines(path: string): string[] | ||||||
| asConfig(pattern?: string): Record<string, string> | ||||||
| asSecrets(pattern?: string): Record<string, string> // base64 encoded | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Manifest | ||||||
|
|
||||||
| ```typescript | ||||||
| interface Manifest { | ||||||
| apiVersion: string | ||||||
| kind: string | ||||||
| metadata: ObjectMeta | ||||||
| [key: string]: unknown | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how can it be done in TS, but this must work for |
||||||
| } | ||||||
|
|
||||||
| interface ObjectMeta { | ||||||
| name: string | ||||||
| namespace?: string | ||||||
| labels?: Record<string, string> | ||||||
| annotations?: Record<string, string> | ||||||
| ownerReferences?: OwnerReference[] | ||||||
| finalizers?: string[] | ||||||
|
Comment on lines
+115
to
+116
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Not needed |
||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Usage Example | ||||||
|
|
||||||
| ```typescript | ||||||
| import { HelmContext, Manifest } from '@nelm/sdk' | ||||||
| import { Values } from './values.types' | ||||||
| import { when } from '@nelm/sdk' | ||||||
|
|
||||||
| export default function render(ctx: HelmContext<Values>): Manifest[] { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to see something like RenderResult here instead of Manifest[].
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, maybe instead of ctx name it $? Will be more familiar |
||||||
| const labels = { | ||||||
| 'app.kubernetes.io/name': ctx.Chart.Name, | ||||||
| 'app.kubernetes.io/instance': ctx.Release.Name, | ||||||
| 'app.kubernetes.io/version': ctx.Chart.AppVersion, | ||||||
| } | ||||||
|
|
||||||
| return [ | ||||||
| // Deployment | ||||||
| { | ||||||
| apiVersion: 'apps/v1', | ||||||
| kind: 'Deployment', | ||||||
| metadata: { | ||||||
| name: ctx.Release.Name, | ||||||
| namespace: ctx.Release.Namespace, | ||||||
| labels, | ||||||
| }, | ||||||
| spec: { | ||||||
| replicas: ctx.Values.replicas, | ||||||
| selector: { matchLabels: labels }, | ||||||
| template: { | ||||||
| metadata: { labels }, | ||||||
| spec: { | ||||||
| containers: [{ | ||||||
| name: ctx.Chart.Name, | ||||||
| image: `${ctx.Values.image.repository}:${ctx.Values.image.tag}`, | ||||||
| }], | ||||||
| }, | ||||||
| }, | ||||||
| }, | ||||||
| }, | ||||||
|
|
||||||
| // Service | ||||||
| { | ||||||
| apiVersion: 'v1', | ||||||
| kind: 'Service', | ||||||
| metadata: { | ||||||
| name: ctx.Release.Name, | ||||||
| namespace: ctx.Release.Namespace, | ||||||
| labels, | ||||||
| }, | ||||||
| spec: { | ||||||
| selector: labels, | ||||||
| ports: [{ port: 80, targetPort: 8080 }], | ||||||
| }, | ||||||
| }, | ||||||
|
|
||||||
| // Conditional Ingress | ||||||
| ...when(ctx.Values.ingress.enabled, [{ | ||||||
| apiVersion: 'networking.k8s.io/v1', | ||||||
| kind: 'Ingress', | ||||||
| metadata: { | ||||||
| name: ctx.Release.Name, | ||||||
| namespace: ctx.Release.Namespace, | ||||||
| }, | ||||||
| spec: { | ||||||
| rules: [{ | ||||||
| host: ctx.Values.ingress.host, | ||||||
| http: { | ||||||
| paths: [{ | ||||||
| path: '/', | ||||||
| pathType: 'Prefix', | ||||||
| backend: { | ||||||
| service: { | ||||||
| name: ctx.Release.Name, | ||||||
| port: { number: 80 }, | ||||||
| }, | ||||||
| }, | ||||||
| }], | ||||||
| }, | ||||||
| }], | ||||||
| }, | ||||||
| }]), | ||||||
|
|
||||||
| // Conditional: check if CRD exists | ||||||
| ...when(ctx.Capabilities.APIVersions.has('monitoring.coreos.com/v1'), [{ | ||||||
| apiVersion: 'monitoring.coreos.com/v1', | ||||||
| kind: 'ServiceMonitor', | ||||||
| metadata: { | ||||||
| name: ctx.Release.Name, | ||||||
| namespace: ctx.Release.Namespace, | ||||||
| }, | ||||||
| spec: { | ||||||
| selector: { matchLabels: labels }, | ||||||
| endpoints: [{ port: 'http' }], | ||||||
| }, | ||||||
| }]), | ||||||
| ] | ||||||
| } | ||||||
| ``` | ||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,100 @@ | ||||||||
| # CLI Commands | ||||||||
|
|
||||||||
| ## TypeScript Chart Commands | ||||||||
|
|
||||||||
| ### nelm chart ts init | ||||||||
|
|
||||||||
| Initialize TypeScript support in a chart. | ||||||||
|
|
||||||||
| ```bash | ||||||||
| nelm chart ts init [path] | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I guess it should make a minimal chart with templates directory by default. But if you pass flag As a first version we can return an error, if |
||||||||
| ``` | ||||||||
|
|
||||||||
| **Arguments:** | ||||||||
| - `path` — Chart directory (default: current directory) | ||||||||
|
|
||||||||
| **Creates:** | ||||||||
| - `ts/package.json` | ||||||||
| - `ts/tsconfig.json` | ||||||||
| - `ts/src/index.ts` | ||||||||
|
|
||||||||
| **Output:** | ||||||||
| ``` | ||||||||
| Created ts/package.json | ||||||||
| Created ts/tsconfig.json | ||||||||
| Created ts/src/index.ts | ||||||||
|
|
||||||||
| Next steps: | ||||||||
| cd ts | ||||||||
| npm install | ||||||||
| npm run generate-types # if values.schema.json exists | ||||||||
| ``` | ||||||||
|
|
||||||||
| ### nelm chart render | ||||||||
|
|
||||||||
| Render chart manifests (Go templates + TypeScript). | ||||||||
|
|
||||||||
| ```bash | ||||||||
| nelm chart render [path] [flags] | ||||||||
| ``` | ||||||||
|
|
||||||||
| **Flags:** | ||||||||
| - `--values, -f` — Values file | ||||||||
| - `--set` — Set values on command line | ||||||||
| - `--output, -o` — Output format (yaml, json) | ||||||||
|
|
||||||||
| **Behavior:** | ||||||||
| 1. Renders Go templates (if `templates/` exists) | ||||||||
| 2. Renders TypeScript (if `ts/` exists) | ||||||||
| 3. Combines and outputs manifests | ||||||||
|
|
||||||||
| ### nelm chart publish | ||||||||
|
|
||||||||
| Package and publish chart to registry. | ||||||||
|
|
||||||||
| ```bash | ||||||||
| nelm chart publish [path] [flags] | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| ``` | ||||||||
|
|
||||||||
| **Behavior:** | ||||||||
| 1. Bundles TypeScript with esbuild → `ts/vendor/bundle.js` | ||||||||
| 2. Packages chart | ||||||||
| 3. Uploads to registry | ||||||||
|
|
||||||||
| ## Existing Commands (unchanged) | ||||||||
|
|
||||||||
| These commands work with both Go templates and TypeScript charts: | ||||||||
|
|
||||||||
| ```bash | ||||||||
| nelm release install <chart> [flags] | ||||||||
| nelm release upgrade <release> <chart> [flags] | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| nelm release uninstall <release> [flags] | ||||||||
| nelm release list [flags] | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| ``` | ||||||||
|
|
||||||||
| ## Example Session | ||||||||
|
|
||||||||
| ```bash | ||||||||
| # Create new chart | ||||||||
| mkdir mychart && cd mychart | ||||||||
| nelm chart create . | ||||||||
|
|
||||||||
| # Add TypeScript support | ||||||||
| nelm chart ts init . | ||||||||
|
|
||||||||
| # Install dependencies | ||||||||
| cd ts && npm install | ||||||||
|
|
||||||||
| # Generate types from schema | ||||||||
| npm run generate-types | ||||||||
|
|
||||||||
| # Develop... | ||||||||
| # Edit src/index.ts | ||||||||
|
|
||||||||
| # Test render | ||||||||
| cd .. | ||||||||
| nelm chart render . --values my-values.yaml | ||||||||
|
|
||||||||
| # Publish | ||||||||
| nelm chart publish . --repo myrepo | ||||||||
| ``` | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no