TypeScript Config: Goodbye YAML Fatigue
A detailed look at configuring Cloudflares native CI/CD Workflows using type-safe TypeScript code instead of complex YAML files.
If you have ever written a GitHub Actions pipeline, you probably know the pain of “YAML Fatigue”. You try to add a simple conditional step or run tests in parallel, only to find yourself struggling with indentation spacing, writing bash scripts inside multi-line strings, and committing 20 times just to debug.
With Cloudflare’s new native CI/CD system, YAML is out. TypeScript is in.
Because a CI/CD pipeline is essentially a set of step-by-step instructions, Cloudflare lets you configure it using type-safe TypeScript code. Here is a detailed, easy-to-understand walkthrough of how to write your configuration.
YAML vs TypeScript#
In a traditional setup, pipelines are static configuration strings. In Cloudflare CI Workflows, pipelines are active programs:
graph LR
YAML["📄 Static YAML<br/>- No loops<br/>- No native functions<br/>- Heavy bash scripts"]
TS["🦕 TypeScript Code<br/>- Loops & Try/Catch<br/>- Parallel execution<br/>- Full autocomplete"]
YAML -->|Replaced by| TS
Writing a CI Pipeline in TypeScript#
Here is a full, detailed example of a CI/CD workflow configured in TypeScript.
import { CIWorkflow, CiRunnerResult, isCiRunnerFailure } from '@cloudflare/ci';
export class MyProjectCI extends CIWorkflow {
async run(event, step) {
let deps: CiRunnerResult;
try {
// 1. Dependency installation with caching
deps = await ci.runner({
name: 'install',
command: 'bun install --frozen-lockfile',
cache: { inputs: ['package.json', 'bun.lock'] },
});
// 2. Run test suites in parallel
await Promise.all([
deps.runner({ name: 'lint', command: 'bun run lint' }),
deps.runner({ name: 'test', command: 'bun run test' }),
deps.runner({ name: 'typecheck', command: 'bun run typecheck' }),
deps.runner({ name: 'build', command: 'bun run build' }),
]);
} catch (failure) {
// 3. Handle errors and call a self-healing AI agent
if (isCiRunnerFailure(failure)) {
const healed = await step.do('heal', async () => {
const healer = await getAgentByName(this.env.HEALER, event.instanceId);
return await healer.heal({ failure, event });
});
throw new CiRunFailedWithFix(failure, healed);
}
throw failure;
}
// 4. Deployment runs only if all parallel steps succeeded
await deps.runner({
name: 'deploy',
command: 'bun wrangler deploy',
});
}
}typescriptDetailed Code Breakdown#
1. Installation & Caching#
deps = await ci.runner({
name: 'install',
command: 'bun install --frozen-lockfile',
cache: { inputs: ['package.json', 'bun.lock'] },
});typescriptInstead of manually caching directories, you pass a cache block pointing to your lockfile. Cloudflare automatically takes a sandbox snapshot of your dependencies and saves it to R2. If the lockfile doesn’t change, the next run loads instantly!
2. Parallel Executions#
await Promise.all([
deps.runner({ name: 'lint', command: 'bun run lint' }),
deps.runner({ name: 'test', command: 'bun run test' }),
...
]);typescriptIn YAML, running steps in parallel requires defining complex jobs and staging matrix rules. In TypeScript, you just use standard JavaScript Promise.all(). Cloudflare spins up isolated sandboxes for each command and executes them concurrently.
3. Self-Healing Try/Catch#
catch (failure) {
if (isCiRunnerFailure(failure)) {
// Trigger LLM Agent to write a patch branch automatically!
}
}typescriptIf a unit test fails, instead of just sending an alert, the catch block catches the runner error, summons an AI agent (like Workers AI), reads the logs, writes a fix, commits it to a new branch, and suggests a pull request to the developer.
Triggering the Build (wrangler.toml)#
To hook up your repository push events to trigger this workflow, configure a trigger binding in your wrangler.toml file:
{
"triggers": {
"events": [
{
"type": "cf.artifacts.repo.pushed",
"filter": {
"namespace": "CI",
"repoName": "my-app"
},
"target": {
"type": "workflow",
"workflow_name": "ci-workflow"
}
}
]
}
}jsonWhenever code is pushed to your Artifacts namespace, Cloudflare starts a new instance of your TypeScript workflow automatically.