Overview
In addition to Helm templates, werf can generate Kubernetes manifests with TypeScript. Helm templates and TypeScript templates can coexist in the same chart — resulting manifests are merged into a single multi-doc YAML document.
TypeScript templates are rendered with Node.js: deploying a chart that contains a ts/ directory requires Node.js 26 or newer and a package manager (npm, pnpm or yarn) installed on the host.
Why TypeScript
Helm’s templating language works well for simple cases but becomes hard to maintain as chart complexity grows: primitive language with lots of gotchas, limited library, performance issues, debug difficulties, poor IDE/editor support and so on. TypeScript in werf solves these problems without complicating the deployment workflow.
Features
- IDE support — full autocompletion, type checking, go-to-definition, and refactoring in any editor with TypeScript support (VS Code, JetBrains, Neovim, etc.).
- Standard syntax — proper functions, loops, and conditionals instead of awkward template engine constructs.
- Pure TypeScript —
tsdirectory is a regular Node.js TypeScript project, and can be rendered without werf withnpm run dev. - Large ecosystem — TypeScript is one of the most popular languages with extensive documentation, community resources, and tooling.
- Almost any third-party TypeScript/JavaScript library can be used, for example kubernetes-models, cdk8s or any other library from the npm ecosystem.
- Testing — test your code using common TypeScript libraries and tooling.
- Isolated environments — npm modules are bundled into the chart by default, so no network calls will be done during the deployment, except to the Kubernetes itself.
- Security — code runs under the Node.js permission model with no access to the network, environment variables, or process execution. Filesystem access is limited to reading chart files.
Quick start
Initialize TypeScript files in an existing chart:
werf chart ts init
It will bootstrap the .helm/ts/ directory, which contains a TypeScript project skeleton and a few files with sample resources. Try modifying ts/src/deployment.ts — for example, change the number of replicas — then check the result:
werf render --dev
To deploy:
werf converge --dev
Chart structure
ts/node_modules/
ts/.dev/
ts/.pnp.*
ts/.yarn/cache/
ts/.yarn/unplugged/
ts/.yarn/__virtual__/
ts/.yarn/install-state.gz
ts/.yarn/build-state.yml
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
# TypeScript chart files
ts/node_modules/
ts/.dev/
ts/.pnp.*
ts/.yarn/cache/
ts/.yarn/unplugged/
ts/.yarn/__virtual__/
ts/.yarn/install-state.gz
ts/.yarn/build-state.yml
apiVersion: v2
name: ts-chart-example
version: 0.1.0
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: ts-chart-example.enabled
Description: ts-chart-example description
Home: https://example.org/home
Icon: https://example.org/icon
Keywords:
- ts-chart-example
Maintainers:
- Email: john@example.com
Name: john
URL: https://example.com/john
Name: ts-chart-example
Sources:
- https://example.org/ts-chart-example
Tags: ts-chart-example
Type: application
Version: 0.1.0
Files:
myfile: "content"
Release:
IsInstall: false
IsUpgrade: true
Name: ts-chart-example
Namespace: ts-chart-example
Revision: 2
Service: Helm
Values:
global:
werf:
name: myapp
version: v3.6.1
repo: example.org/mycompany/myapp
env: production
images:
app:
registry: example.org
namespace: mycompany
name: myapp
tag: a1b2c3d4-1234567890
digest: "sha256:abcdef1234567890"
tag_digest: "a1b2c3d4-1234567890@sha256:abcdef1234567890"
image: example.org/mycompany/myapp
repository: mycompany/myapp
ref: "example.org/mycompany/myapp:a1b2c3d4-1234567890@sha256:abcdef1234567890"
ref_tag: "example.org/mycompany/myapp:a1b2c3d4-1234567890"
repository_ref: "mycompany/myapp:a1b2c3d4-1234567890@sha256:abcdef1234567890"
repository_tag: "mycompany/myapp:a1b2c3d4-1234567890"
name_ref: "myapp:a1b2c3d4-1234567890@sha256:abcdef1234567890"
name_tag: "myapp:a1b2c3d4-1234567890"
commit:
date:
human: "2025-01-15 12:00:00 +0000"
unix: 1736942400
hash: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
image:
repository: nginx
tag: latest
replicaCount: 1
service:
enabled: true
port: 80
type: ClusterIP
{
"name": "ts-chart-example-ts",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ts-chart-example-ts",
"version": "0.0.0",
"dependencies": {
"@nelm/chart-ts-sdk": "^0.2.0"
},
"devDependencies": {
"typescript": "~6.0.3"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@nelm/chart-ts-sdk": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@nelm/chart-ts-sdk/-/chart-ts-sdk-0.2.0.tgz",
"integrity": "sha512-BpV2RppfbBjK6gqsAxg4aZcmeNEwSg3cc1p6J7ca320B6y/a0a0gA6Y6R8b8a9/wAZq8zxAGcuKc9n1WSKl/EQ==",
"license": "Apache-2.0",
"dependencies": {
"esbuild": "0.28.2",
"js-yaml": "^5.5.0"
},
"bin": {
"nelm-chart-ts": "dist/cli.js"
},
"engines": {
"node": ">=26"
}
},
"node_modules/argparse": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-3.0.4.tgz",
"integrity": "sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "PSF-2.0"
},
"node_modules/esbuild": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.2",
"@esbuild/android-arm": "0.28.2",
"@esbuild/android-arm64": "0.28.2",
"@esbuild/android-x64": "0.28.2",
"@esbuild/darwin-arm64": "0.28.2",
"@esbuild/darwin-x64": "0.28.2",
"@esbuild/freebsd-arm64": "0.28.2",
"@esbuild/freebsd-x64": "0.28.2",
"@esbuild/linux-arm": "0.28.2",
"@esbuild/linux-arm64": "0.28.2",
"@esbuild/linux-ia32": "0.28.2",
"@esbuild/linux-loong64": "0.28.2",
"@esbuild/linux-mips64el": "0.28.2",
"@esbuild/linux-ppc64": "0.28.2",
"@esbuild/linux-riscv64": "0.28.2",
"@esbuild/linux-s390x": "0.28.2",
"@esbuild/linux-x64": "0.28.2",
"@esbuild/netbsd-arm64": "0.28.2",
"@esbuild/netbsd-x64": "0.28.2",
"@esbuild/openbsd-arm64": "0.28.2",
"@esbuild/openbsd-x64": "0.28.2",
"@esbuild/openharmony-arm64": "0.28.2",
"@esbuild/sunos-x64": "0.28.2",
"@esbuild/win32-arm64": "0.28.2",
"@esbuild/win32-ia32": "0.28.2",
"@esbuild/win32-x64": "0.28.2"
}
},
"node_modules/js-yaml": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.5.0.tgz",
"integrity": "sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^3.0.3"
},
"bin": {
"js-yaml": "bin/js-yaml.mjs"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
{
"name": "ts-chart-example-ts",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"dev": "nelm-chart-ts dev --input-file input.example.yaml",
"typecheck": "tsc"
},
"dependencies": {
"@nelm/chart-ts-sdk": "^0.2.0"
},
"devDependencies": {
"typescript": "~6.0.3"
}
}
import type { WerfRenderContext } from '@nelm/chart-ts-sdk';
import { getFullname, getLabels, getSelectorLabels } from './helpers.ts';
export function newDeployment($: WerfRenderContext): object {
const name = getFullname($);
return {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name,
labels: getLabels($),
},
spec: {
replicas: $.Values.replicaCount ?? 1,
selector: {
matchLabels: getSelectorLabels($),
},
template: {
metadata: {
labels: getSelectorLabels($),
},
spec: {
containers: [
{
name: name,
image: ($.Values.image?.repository ?? 'nginx') + ':' + ($.Values.image?.tag ?? 'latest'),
ports: [
{
name: 'http',
containerPort: $.Values.service?.port ?? 80,
},
],
},
],
},
},
},
};
}
import type { WerfRenderContext } from '@nelm/chart-ts-sdk';
/**
* Truncate string to max length, removing trailing hyphens.
*/
export function trunc(str: string, max: number): string {
if (str.length <= max) return str;
return str.slice(0, max).replace(/-+$/, '');
}
/**
* Get the fully qualified app name.
* Truncated at 63 chars (DNS naming spec limit).
*/
export function getFullname($: WerfRenderContext): 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);
}
export function getLabels($: WerfRenderContext): Record<string, string> {
return {
'app.kubernetes.io/name': $.Chart.Name,
'app.kubernetes.io/instance': $.Release.Name,
};
}
export function getSelectorLabels($: WerfRenderContext): Record<string, string> {
return {
'app.kubernetes.io/name': $.Chart.Name,
'app.kubernetes.io/instance': $.Release.Name,
};
}
import { type WerfRenderContext, type RenderResult, render } from '@nelm/chart-ts-sdk';
import { newDeployment } from './deployment.ts';
import { newService } from './service.ts';
function generate($: WerfRenderContext): RenderResult {
const manifests: object[] = [];
manifests.push(newDeployment($));
if ($.Values.service?.enabled !== false) {
manifests.push(newService($));
}
return { manifests };
}
await render(generate);
import type { WerfRenderContext } from '@nelm/chart-ts-sdk';
import { getFullname, getLabels, getSelectorLabels } from './helpers.ts';
export function newService($: WerfRenderContext): object {
return {
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: getFullname($),
labels: getLabels($),
},
spec: {
type: $.Values.service?.type ?? 'ClusterIP',
ports: [
{
port: $.Values.service?.port ?? 80,
targetPort: 'http',
},
],
selector: getSelectorLabels($),
},
};
}
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"strict": true,
"skipLibCheck": true,
"types": []
},
"include": ["src"]
}
replicaCount: 1
image:
repository: nginx
tag: latest
service:
enabled: true
type: ClusterIP
port: 80
Developing a chart with TypeScript templates
Install Node.js 26 or newer and set up TypeScript support in your IDE/editor (VS Code, JetBrains, Neovim, etc.).
Initialize TypeScript files in the chart if not already initialized:
werf chart ts init
Open the ts/ directory in your editor as a regular Node.js/TypeScript project. You can work with it the same way you would with any TypeScript codebase — run scripts, write tests, use a debugger.
The codebase can be organized as you wish. The only requirement is that ts/src/index.ts exists, and render function from @nelm/chart-ts-sdk must be called. Otherwise, no TypeScript rendering happens.
To debug templates rendering in an environment that is very close to how werf runs Node.js, you can use the dev script from ts/package.json:
cd .helm/ts
npm run dev
TypeScript engine will call render function from ts/src/index.ts with the example context from ts/input.example.yaml. The resulting YAML will be printed to the console below the Rendered manifests: message.
Install libraries using npm install, for example, try to install kubernetes-models — library for strict Kubernetes resource typing:
npm install kubernetes-models
The dependency is added to package.json automatically. Now you can import and use it:
// .helm/ts/src/deployment.ts:
import { Deployment } from 'kubernetes-models/apps/v1';
export function newDeployment($: WerfRenderContext): object {
return new Deployment({
metadata: { name: 'myapp' },
spec: {
// other fields
},
}).toJSON();
}
To ensure that everything actually works when werf renders the chart, run:
werf lint --dev
werf render --dev
How to deploy a chart with TypeScript templates
Simply run werf converge: TypeScript templates will be rendered with Node.js and deployed.
Note: According to giterminism policies, all changed files must be committed.
Deploying into isolated environments
An isolated environment requires only Node.js on the target machine and the chart itself to be self-contained:
- Publish the chart:
werf bundle publish --repo example.org/mycompany/myappAll npm modules will be minified and bundled inside, so that the chart can be installed even without Internet access.
- On the target machine with an isolated environment (no network access), run:
werf bundle apply --repo example.org/mycompany/myappTypeScript templates will be rendered and deployed using pre-compiled files from the chart bundle.
To change such a chart and pack it again without network access, pass --ts-no-rebuild to nelm chart pack: it reuses the pre-compiled files instead of rebuilding them, see Changing a published chart. Helm, Argo CD and Flux do not run TypeScript templates, so they do not deploy the resources these templates generate.
SDK API overview
TypeScript engine uses the @nelm/chart-ts-sdk package.
“render” and “generate” functions
index.ts must call the render() function. The function generate(), which will actually generate the manifests, should be passed to the render() function as an argument, for example:
// .helm/ts/src/index.ts:
await render(generate);
“WerfRenderContext” object
The generate function receives the root context in the $ variable of type WerfRenderContext — the same context as in Helm templates:
| Field | Type | Description |
|---|---|---|
$.Values |
WerfServiceValues |
Chart parameters + service values at $.Values.global.werf |
$.Release |
Release |
Release information |
$.Chart |
ChartMetadata |
Metadata from Chart.yaml |
$.Capabilities |
Capabilities |
Cluster capabilities (API versions, Kubernetes version) |
$.Files |
Record<string, Uint8Array> |
Raw chart files (except templates/ and ts/) |
See the example context in ts/input.example.yaml. For details on parameters and how they are constructed, see Parametrize templates.
“RenderResult” object
The generate function returns RenderResult — an object with a manifests array. Each element is a plain JavaScript object representing a Kubernetes resource. Example output:
{
"manifests": [
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": { "name": "myapp" },
"spec": { "..." }
},
{
"apiVersion": "v1",
"kind": "Service",
"metadata": { "name": "myapp" },
"spec": { "..." }
}
]
}
Each object is serialized to YAML and included in the final rendered output.