Skip to main content

GitHub Actions

Generate a CI workflow that tests against multiple Node.js versions using lifting over a version list.

Source

examples/github-actions/main.rank
/// Generates a GitHub Actions CI workflow.
/// Shows string template composition, lifting map over a job matrix,
/// and conditional step inclusion via ternary expressions.

use std::collections::{ map }
use std::Emit
use std::Path

Step = Object {
name: string,
uses?: string,
run?: string,
with?: Object { ...: string },
env?: Object { ...: string },
}

Job = Object {
name: string,
`runs-on`: string,
steps: [Step],
}

Workflow = Object {
name: string,
on: Object {
push: Object { branches: [string] },
pull_request: Object { branches: [string] },
},
jobs: Object { ...: Job },
}

node_versions: [string] = [`18`, `20`, `22`]

checkout_step: Step = {
name: `Checkout`,
uses: `actions/checkout@v4`,
}

setup_node = |version: string| -> Step {
return {
name: `Setup Node ${version}`,
uses: `actions/setup-node@v4`,
with: { `node-version`: version },
}
}

install_step: Step = {
name: `Install dependencies`,
run: `npm ci`,
}

test_step: Step = {
name: `Run tests`,
run: `npm test`,
}

build_job = |version: string| -> Job {
return {
name: `build-node-${version}`,
`runs-on`: `ubuntu-latest`,
steps: [
checkout_step,
setup_node(version),
install_step,
test_step,
],
}
}

jobs: [Job] = node_versions |> map(|v: string| build_job(v))

workflow: Workflow = {
name: `CI`,
on: {
push: { branches: [`main`] },
pull_request: { branches: [`main`] },
},
jobs: {
`build-18`: jobs[0],
`build-20`: jobs[1],
`build-22`: jobs[2],
},
}

pub main = || Emit::manifest({
entries: [
{
path: Path::join([`.github`, `workflows`, `ci.yml`]),
format: `yaml`,
value: workflow,
},
]
})

Output

# .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-18:
name: build-node-18
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node 18
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
build-20:
# ... same pattern for 20 and 22

Key concepts

  • map over a listnode_versions |> map(|v| build_job(v)) produces one Job per version with no boilerplate.
  • String interpolation in keys — backtick strings like `build-node-${version}` work in field names and values.
  • Quoted field names`runs-on` uses a backtick string as a field name to handle the hyphen.
  • Path::join — constructs the nested output path .github/workflows/ci.yml safely.

Run it

rank examples/github-actions