TypeScript SDK for generating Kubernetes manifests with TypeScript instead of Helm templates.
Runs under Deno. Reads a YAML render context from a file, passes it to your handler, and writes the resulting manifests as a multi-document YAML file.
npm install @nelm/chart-ts-sdk
import {RenderContext, RenderResult, runRender} from "@nelm/chart-ts-sdk";
function render($: RenderContext): RenderResult {
return {
manifests: [
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: $.Release.Name,
namespace: $.Release.Namespace,
labels: {
'app.kubernetes.io/name': $.Chart.Name,
'app.kubernetes.io/version': $.Chart.AppVersion,
'app.kubernetes.io/instance': $.Release.Name,
},
},
spec: {
replicas: $.Values.replicaCount ?? 1,
selector: {
matchLabels: {
'app.kubernetes.io/name': $.Chart.Name,
'app.kubernetes.io/instance': $.Release.Name,
},
},
template: {
metadata: {
labels: {
'app.kubernetes.io/name': $.Chart.Name,
'app.kubernetes.io/instance': $.Release.Name,
},
},
spec: {
containers: [
{
name: $.Chart.Name,
image: `${$.Values.image?.repository}:${$.Values.image?.tag}`,
ports: [
{
name: 'http',
containerPort: $.Values.service?.port ?? 80,
},
],
},
],
},
},
},
},
],
};
}
runRender(render);runRender is the entry point. It:
- Parses CLI arguments
--input-fileand--output-file - Reads the input file as YAML and deserializes it into a
RenderContext - Calls your handler with the context
- Validates that the result contains a non-empty
manifestsarray - Serializes each manifest to YAML and writes them to the output file, separated by
---
deno run render.ts --input-file context.yaml --output-file manifests.yaml
Runs the render pipeline. Accepts a handler function that receives RenderContext and returns RenderResult.
type RenderHandler = ($: RenderContext) => Promise<RenderResult> | RenderResult;The handler can be sync or async.
The full context passed to your handler. By convention, the parameter is named $.
interface RenderContext {
Values: Record<string, any>; // User-supplied values (values.yaml)
Release: Release; // Release metadata
Chart: ChartMetadata; // Chart.yaml contents
Capabilities: Capabilities; // Cluster capabilities
Runtime: Record<string, any>; // Runtime-specific data
Files: Record<string, Uint8Array>; // Raw chart files
}interface Release {
Name: string; // Release name
Namespace: string; // Target namespace
Revision: number; // Release revision number
IsInstall: boolean; // true on initial install
IsUpgrade: boolean; // true on upgrade
Service: string; // Rendering service name
}interface ChartMetadata {
Name: string;
Version: string;
AppVersion: string;
Description: string;
Home: string;
Icon: string;
APIVersion: string;
Condition: string;
Tags: string[];
Type: string;
Keywords: string[];
Sources: string[];
Maintainers: Maintainer[];
Annotations: Record<string, string>;
}interface Capabilities {
APIVersions: string[]; // Available API versions in the cluster
KubeVersion: KubeVersion; // Kubernetes version info
HelmVersion: HelmVersion; // Helm version info
}
interface KubeVersion {
Version: string; // e.g. "v1.28.0"
Major: string;
Minor: string;
}
interface HelmVersion {
Version: string;
GitCommit: string;
GitTreeState: string;
GoVersion: string;
}interface RenderResult {
manifests: object[] | null; // Array of Kubernetes manifest objects
}The manifests array must be non-empty — runRender throws if it's null, undefined, or empty.
A more complete example with helper functions.
Input file (context.yaml):
Capabilities:
APIVersions:
- v1
HelmVersion:
go_version: go1.25.0
version: v3.20
KubeVersion:
Major: "1"
Minor: "35"
Version: v1.35.0
Chart:
APIVersion: v2
Annotations:
anno: value
AppVersion: 1.0.0
Condition: mychart.enabled
Description: mychart description
Home: https://example.org/home
Icon: https://example.org/icon
Keywords:
- mychart
Maintainers:
- Email: john@example.com
Name: john
URL: https://example.com/john
Name: mychart
Sources:
- https://example.org/mychart
Tags: mychart
Type: application
Version: 1.0.0
Files:
myfile: "content"
Release:
IsInstall: false
IsUpgrade: true
Name: mychart
Namespace: mychart
Revision: 2
Service: Helm
Values:
image:
repository: nginx
tag: latest
replicaCount: 1
service:
enabled: true
port: 80
type: ClusterIPRender script (render.ts):
import {RenderContext, RenderResult, runRender} from "@nelm/chart-ts-sdk";
function trunc(str: string, max: number): string {
if (str.length <= max) return str;
return str.slice(0, max).replace(/-+$/, '');
}
function fullname($: RenderContext): string {
if ($.Values.fullnameOverride) {
return trunc($.Values.fullnameOverride, 63);
}
const chartName = $.Values.nameOverride || $.Chart.Name;
if ($.Release.Name.includes(chartName)) {
return trunc($.Release.Name, 63);
}
return trunc(`${$.Release.Name}-${chartName}`, 63);
}
function labels($: RenderContext): Record<string, string> {
return {
'app.kubernetes.io/name': $.Chart.Name,
'app.kubernetes.io/instance': $.Release.Name,
'app.kubernetes.io/version': $.Chart.AppVersion,
'app.kubernetes.io/managed-by': $.Release.Service,
};
}
function selectorLabels($: RenderContext): Record<string, string> {
return {
'app.kubernetes.io/name': $.Chart.Name,
'app.kubernetes.io/instance': $.Release.Name,
};
}
function render($: RenderContext): RenderResult {
const name = fullname($);
return {
manifests: [
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: name,
labels: labels($),
},
spec: {
replicas: $.Values.replicaCount ?? 1,
selector: {
matchLabels: selectorLabels($),
},
template: {
metadata: {
labels: selectorLabels($),
},
spec: {
containers: [
{
name: name,
image: `${$.Values.image?.repository}:${$.Values.image?.tag}`,
ports: [
{
name: 'http',
containerPort: $.Values.service?.port ?? 80,
},
],
},
],
},
},
},
},
],
};
}
runRender(render);Run:
deno run render.ts --input-file context.yaml --output-file manifests.yaml