Skip to content

Migration Guide

Franklin García edited this page Sep 13, 2026 · 4 revisions

Migration Guide

This guide moves older Timonel code toward the current typed-first API without forcing a single large rewrite. For a production migration that explicitly forbids raw manifest fallbacks and TypeScript casts, use Production Typed-Only Migration.

Migration priorities

Use this order:

  1. migrate raw Kubernetes YAML to typed cdk8s/cdk8s-plus constructs;
  2. replace string values references with valuesRef<T>();
  3. replace string-oriented condition/range/with helpers where ValuesRef is sufficient;
  4. replace synchronous synthesis with the asynchronous contract;
  5. leave narrow object-form fallbacks only where a typed resource does not exist.

1. Raw YAML to typed resources

Before: raw YAML

chart.addManifest(
  `apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  mode: production`,
  'Config',
);

After: typed ConfigMap

import * as kplus from 'cdk8s-plus-33';

new kplus.ConfigMap(chart.getChart(), 'Config', {
  metadata: { name: 'app-config' },
  data: { mode: 'production' },
});

The raw-string addManifest() overload is deprecated.

2. addTemplateManifest() to constructs or ValuesRef

addTemplateManifest() remains available for compatibility but is deprecated and planned for removal in the next major release.

First ask whether the resource can be represented with cdk8s-plus. If only a few fields require Helm templating, prefer a focused Timonel-managed object structure rather than making the entire resource raw YAML.

3. String .Values references to ValuesRef

Before: manual values expression

Older code often constructed expressions manually:

createHelmExpression('{{ .Values.image.tag | quote }}');

After: ValuesRef property

interface Values {
  image: {
    tag: string;
  };
}

const v = valuesRef<Values>();
const tag = v.image.tag.quote();

The compiler now checks image.tag against your values interface.

4. String conditionals to typed conditions

Before: string conditional

helmIf('.Values.autoscaling.enabled', enabledContent);

After: typed conditional

const enabled = v.autoscaling.enabled.eq(true);
const conditional = v.autoscaling.enabled.if(enabled, enabledContent);

Use the lower-level string helper only when the typed condition API cannot express the Helm logic.

5. String ranges to typed ranges

Before: string range

helmRange('$item', '.Values.env', content);

After: typed range

interface Values {
  env: Array<{ name: string; value: string }>;
}

const v = valuesRef<Values>();

const env = v.env.range((item) => ({
  name: item.name,
  value: item.value,
}));

The callback now exposes the array element type and a real scoped Helm proxy at runtime.

6. String with to typed with

Before: string with

helmWith('.Values.database', content);

After: typed with

const database = v.database.with((db) => ({
  host: db.host,
  port: db.port,
}));

Nested access stays typed inside the callback.

7. Reserved ValuesRef keys

Older schemas may contain keys that now collide with ValuesRef methods or root helpers.

interface Values {
  release: { name: string };
  settings: { default: string };
}

Use the typed accessor:

v.at('release').name;
v.settings.at('default');

Do not rename production Helm values solely to work around the proxy API unless you want that breaking values-schema change.

8. Async synthesis contract

toSynthArray() now always returns a Promise.

Before: synchronous assumption

const assets = chart.toSynthArray();

Code that assumed an array synchronously must change.

After: asynchronous contract

const assets = await chart.toSynthArray();

toSynthArraySync() remains as a deprecated compatibility method for charts without a policy engine.

9. Existing construct trees

If old code created an App but Timonel ignored it, current Rutter honors scope:

const app = new App();
const chart = new Rutter({
  scope: app,
  meta: { name: 'service', version: '1.0.0' },
});

You can attach additional constructs through chart.getChart().

10. Object-form addManifest() argument order

The supported signature is:

addManifest(manifestObject, id)

Correct:

chart.addManifest(
  {
    apiVersion: 'example.io/v1',
    kind: 'Widget',
    metadata: { name: 'widget' },
  },
  'Widget',
);

If an older example uses addManifest('Widget', object), reverse the arguments.

11. CLI projects

The CLI remains supported, but the library API is primary. Existing CLI projects do not need to be rewritten solely because of this documentation change.

When you edit a generated chart, prefer moving new Kubernetes resources to getChart() plus cdk8s-plus constructs instead of adding more raw/string-based manifest logic.

Validation checklist

After migration:

pnpm typecheck
pnpm build
helm lint ./dist/my-chart
helm template test ./dist/my-chart

For a Timonel contribution, also run the repository checks described in Contributing.

Deprecation summary

API Status Preferred path
addManifest(object, id) Supported fallback cdk8s-plus first
addManifest(yaml, id) Deprecated typed construct or object
addTemplateManifest() Deprecated typed construct + ValuesRef
toSynthArray() Current await it
toSynthArraySync() Deprecated async synthesis
helmIf / helmRange / helmWith Compatibility/lower-level ValuesRef where possible

Clone this wiki locally