Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/proposals/go-template-alternative/README.md
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
241 changes: 241 additions & 0 deletions docs/proposals/go-template-alternative/api.md
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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no


// Serialization
toYaml(obj: unknown): string
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of these functions not needed. Let the user use the libraries he wants

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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it

interface File {
  Name string
  Data []byte
}

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 data, spec, or whatever else, or a combination of.

}

interface ObjectMeta {
name: string
namespace?: string
labels?: Record<string, string>
annotations?: Record<string, string>
ownerReferences?: OwnerReference[]
finalizers?: string[]
Comment on lines +115 to +116
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ownerReferences?: OwnerReference[]
finalizers?: string[]

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[] {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see something like RenderResult here instead of Manifest[].

interface RenderResult {
  ApiVersion: string // v1 for now
  Manifests: []Manifest
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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' }],
},
}]),
]
}
```
100 changes: 100 additions & 0 deletions docs/proposals/go-template-alternative/cli.md
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]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nelm chart ts init [path]
nelm chart init [path]

I guess it should make a minimal chart with templates directory by default. But if you pass flag --only-ts, then it will create ts directory in the [path], without touching anything else.

As a first version we can return an error, if --only-ts is not specified, with something like only the --only-ts mode is implemented for now. So the user can create boilerplate ts files, but we won't bother will fully implementing the command for now.

```

**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]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nelm chart publish [path] [flags]
nelm chart upload [path] [flags]

```

**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]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nelm release upgrade <release> <chart> [flags]

nelm release uninstall <release> [flags]
nelm release list [flags]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nelm release list [flags]
nelm release rollback [flags]
nelm release plan intsall [flags]

```

## 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
```
Loading