Add type definitions, code completion, control flow to YAML via TypeScript

Downloads in past


1.0.05 years ago5 years agoMinified + gzip package size for ts-yaml in KB


YAML is awesome!
  • Easy to learn.
  • Minimal syntax.

But it has some drawbacks, especially as YAML files scale.
  • No types/code completion.
  • Ambiguous syntax.
  • No control flow (e.g. ternary operator, functions, map).

This is an experiment to define types for YAML in TypeScript, with the goal of easily generating typechecked YAML.


Buildkite pipleines

Here's a sample pipelines.yml file for Buildkite CI:
  - command: "FIXTURE=cplusplus,schema-cplusplus,kotlin,graphql .buildkite/build-pr.sh"
    label: "C++ Kotlin GraphQL"

  - command: "FIXTURE=java,schema-java,schema-json-csharp .buildkite/build-pr.sh"
    label: "java schema-json-c#"

  - command: "FIXTURE=typescript,schema-typescript,javascript,schema-javascript,flow,schema-flow,json-ts-csharp .buildkite/build-pr.sh"
    label: "typescript javascript flow"

  - command: "FIXTURE=swift,schema-swift,rust,schema-rust,elm,schema-elm .buildkite/build-pr.sh"
    label: "swift rust elm"

  - command: "FIXTURE=csharp,schema-csharp,ruby,schema-ruby,golang,schema-golang .buildkite/build-pr.sh"
    label: "csharp ruby golang"

Let's write the type of the YAML in TypeScript. Immediately we have a lot more information than is evident in the YAML sample:
interface Step {
  command: string | string[];
  label?: string;
  branches?: string;
  env?: { [name: string]: string };
  agents?: { [key: string]: string };
  artifact_paths?: string;
  parallelism?: number;
  concurrency?: number;
  concurrency_group?: string;
  timeout_in_minutes?: number;
  skip?: boolean | string;
    | { automatic: boolean | AutomaticRetryConditions }
    | { manual: boolean | ManualRetryConditions };

interface AutomaticRetryConditions {
  exit_status?: "*" | number;
  limit?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;

interface ManualRetryConditions {
  allowed?: boolean;
  reason?: string;
  permit_on_pass?: boolean;

declare var steps: Step[];

Let's make a file named pipeline.yml.ts and define the steps global variable, and we immediately get code completion where we would otherwise have to search Buildkite's docs:

We can finish defining our CI steps:
import "yaml/buildkite";

const fixtures = [

steps = fixtures.map(fixture => ({
  command: `FIXTURE=${fixture} .buildkite/build-pr.sh`,
  label: fixture

  • steps is typechecked
  • String interpolation
  • Data (list of fixtures)
  • map

Output YAML with ts-yaml pipeline.yaml.ts.