cinzel github -hConvert HCL files containing workflow, job, step, and action blocks into GitHub Actions YAML files.
cinzel github parse --file ./cinzel/steps.hcl --output-directory .github/workflowsConvert GitHub Actions workflow YAML, composite action YAML, or step-only YAML back to HCL.
cinzel github unparse --file ./.github/workflows/steps.yaml --output-directory ./cinzelUse --dry-run to print generated files to stdout.
step "checkout" {
uses {
action = "actions/checkout"
version = "v4"
}
}
step "test" {
run = "go test ./..."
}
job "build" {
runs_on {
runners = "ubuntu-latest"
}
steps = [
step.checkout,
step.test,
]
}
workflow "ci" {
filename = "ci"
on "push" {
branches = ["main"]
}
jobs = [
job.build,
]
}step "setup" {
name = "Setup Node"
uses {
action = "actions/setup-node"
version = "v4"
}
with {
name = "node-version"
value = "20"
}
}
step "build" {
name = "Build"
run = "npm run build"
}
action "my_action" {
filename = "my-action"
name = "My Composite Action"
description = "Runs setup and build"
input "node_version" {
description = "Node.js version"
required = true
default = "20"
}
output "result" {
description = "Build result"
value = "$${{ steps.build.outputs.result }}"
}
runs {
using = "composite"
steps = [step.setup, step.build]
}
}Actions are written to <output-directory>/<filename>/action.yml during parse.
workflow.jobs,job.steps, andaction.runs.stepsare explicit references (job.<id>andstep.<id>).- YAML unparse generates stable HCL identifiers (sanitized when YAML keys contain
-). - Document type is auto-detected during unparse: workflow (has
on/jobs), action (hasname/runs, noon/jobs), or step-only. - Parse schema is defined by typed HCL structs in
provider/github/config.go; avoid schema key allowlist tables. - Unparse schema validation uses strict typed YAML decode (
goccy/go-yamlstrict mode), not manual key allowlists.
- HCL attribute:
depends_on - GitHub YAML key:
needs - Mapping rule:
depends_on<->needs: - HCL
needsis not supported.
The same rules are enforced in both directions (HCL → YAML and YAML → HCL):
- A
workflowmust define at least oneontrigger and at least one job reference. - A normal
job(withoutuses) must defineruns_onand at least one referenced step. - A reusable
job(withuses) cannot defineruns_onorsteps. withandsecretsare only valid for reusable jobs (usesset).permissionsscopes and levels are validated against the known GitHub Actions set.on.schedulecron expressions are validated (5-field format, value ranges).${{ }}expression syntax is checked for balanced delimiters and non-empty bodies.- Step
usesreferences are validated for correct format (owner/repo@ref,./path, ordocker://image). - An
actionmust definenameandruns.using.
Test coverage includes golden fixtures, a fixture-driven compatibility matrix under provider/github/testdata/fixtures/matrix, strict validation checks, semantic roundtrip tests, and benchmarks under provider/github/*_test.go.
- Add new parse scenarios in
provider/github/testdata/fixtures/matrix/parse. - Add new unparse scenarios in
provider/github/testdata/fixtures/matrix/unparse. - For valid parse scenarios, add
<name>.hcland<name>.golden.yaml. - For invalid parse scenarios, add
<name>.hcland<name>.error.txt. - For valid unparse scenarios, add
<name>.yamland<name>.roundtrip.golden.yaml. - For invalid unparse scenarios, add
<name>.yamland<name>.error.txt. - Keep
.error.txtmessages focused on stable substrings, not full error text.
- HCL
workflow,job,step, andactionreference graph with explicit references (workflow.jobs,job.steps,job.depends_on,action.runs.steps). - Action support for all
runs.usingtypes:composite,node20, anddocker, in both parse and unparse directions. actionblocks supportinput,output,runs, andbrandingsub-blocks, written to<filename>/action.yml.- Common workflow triggers and event maps, including empty event blocks for trigger-only events.
- YAML workflow
onshorthand forms (on: pushandon: [push, pull_request]) are normalized during unparse. - YAML
on.schedulelist form is normalized to HCLcronlist inon "schedule"blocks. - Standard jobs (
runs-on,steps) and reusable jobs (uses,with,secrets) with strict validation. - Job-level common keys parse/unparse:
if,timeout-minutes,continue-on-error,permissions,defaults,concurrency,container,services,environment, andstrategy(matrix,include,exclude,fail-fast,max-parallel). - Step
runand structureduses { action, version }parsing and unparsing. - GitHub expression strings (
${{ ... }}) are preserved across workflow, job, and step fields in parse/unparse flows. - Expression syntax validation (balanced
${{ }}delimiters, non-empty bodies). - Permissions validation (known scopes and levels).
- Cron expression validation (5-field format, value ranges).
- Uses reference validation (
owner/repo@ref,./path,docker://image). - Strategy matrix normalization for supported shapes (
matrix.variablein HCL and matrix axes in YAML). - Auto-detection of document type during unparse (workflow, action, or step-only).
- Semantic roundtrip stability for covered fixtures (HCL → YAML → HCL → YAML).
- Not every GitHub Actions schema edge case or uncommon field combination is covered. The most common workflow, job, step, and action fields are supported.
- Roundtrip output is semantically stable but not byte-stable: key ordering and formatting may normalize even when the meaning is preserved.
- Parse output uses unquoted
on(never"on"). - Parse output uses 2-space YAML indentation.
- Parse output top-level key order is deliberate (
name,on,jobs, then remaining keys). - Parse output includes cinzel provider markers in generated YAML headers (
generated-byandcinzel-provider). - Parse cleanup prunes stale workflow YAML only when marker ownership matches the current provider.
- Unparse output formats HCL with clear section separators and trailing commas in reference lists.
- Identifier normalization is stable: YAML names are sanitized to valid HCL identifiers when needed.
- Expression escaping is stable in HCL output (
${{ ... }}in YAML becomes$${{ ... }}in HCL string literals).
- If a stale YAML file is not removed, confirm it still has cinzel markers and matches the current provider (
generated-by: cinzelandcinzel-provider: github). - If marker checks pass but deletion still fails, confirm output directory write/delete permissions for the current user/CI runner.