Skip to content

CI rendering

What ocx-catalog ci [--check] emits and the rules it enforces (src/ci/). This is a reference to the mechanics — for a worked setup, see Deploy on GitHub Pages or Deploy on GitLab Pages.

Output file per forge

ci.forge Output path
github .github/workflows/catalog-ci.yml
gitlab .gitlab-ci/catalog.yml

The GitLab output is a job file meant to be pulled in with include: from the repository's own root .gitlab-ci.yml — this renderer never writes a root .gitlab-ci.yml itself.

Generated-file header contract

Every file this renderer writes starts with a literal first line:

# generated by ocx-catalog ci v1 — do not edit

The version number is TOOL_HEADER_VERSION, bumped only when the render/discovery/normalize contract changes in a way an older tool version could no longer safely parse. That line is the entire "is this file ours" test: a committed file with no header, or a header that doesn't match the pattern, is invisible to this tool — never read for pins, never drift-checked, never overwritten. A discovered file whose header names a version newer than the running tool supports is a hard error (TOOL_TOO_OLD) rather than a best-effort parse of a contract the tool doesn't know yet.

--check: drift detection

ocx-catalog ci --check re-renders the job set in memory and compares it against what's committed, through a normalization pass (normalizeForDrift) rather than a raw byte comparison: line endings are unified, and the @ref/trailing comment on a uses: (GitHub) or image: (GitLab) line is stripped before comparing. A Renovate/Dependabot pin bump on the committed file is therefore never read as drift — only a structural change, or a different action/image entirely, trips it. A missing file also counts as drift. Any drift throws, naming every mismatched file, and the CLI maps that to exit code 65.

Pin carry-forward

GitHub Actions pins (uses: owner/action@<sha>) are scraped out of every discovered generated file and carried forward into the next render, so a Renovate-bumped SHA survives a re-render instead of reverting to the template default. GitLab jobs pin by image: instead and carry no uses: pins at all.

Defaults, used when there's nothing to carry forward:

Action Default pin
actions/checkout 34e114876b0b11c390a56381ad16ebd13914f8d5 (v4.3.1)
actions/setup-node 820762786026740c76f36085b0efc47a31fe5020 (v7.0.0)
oven-sh/setup-bun 0c5077e51419868618aeaa5fe8019c62421857d6 (v2.2.0)

Two situations fall back to the default and warn on stderr instead of carrying a committed pin forward:

  • Conflicting pins — the same action pinned to more than one distinct SHA across the discovered files.
  • A mutable ref — the committed uses: value isn't a full 40-hex commit SHA (a branch, a tag, @main).

The mutable-ref case exists because drift normalization strips @ref before comparing: a hand-edit from a real SHA to @main would otherwise be invisible to the generated verify-catalog-ci/catalog:verify job, and the next render would silently adopt that mutable ref as the new pin. Refusing to carry it forward means a mutable ref can never survive more than one render cycle.

ci config block

{
  "ci": {
    "forge": "github",
    "packageManager": "npm",
    "verifyCi": true
  }
}
Field Values Default
forge "github" | "gitlab" required
packageManager "npm" | "bun" "npm"
verifyCi boolean true

packageManager is a closed union — any other value fails config loading rather than rendering CI that fails at its own install step. verifyCi: false omits the verify job from the rendered output.

ci is the one config object whose unknown keys are tolerated: every other object in catalog.config.json rejects a key it doesn't recognize, but an unknown key inside ci is passed through untouched (forward-compat for a newer tool version's own ci fields).

Running ocx-catalog ci against a config with no ci block at all fails with MISSING_CI_CONFIG — there's nothing to render.

Templates

Template Renders
templates/ci/github-ci.yml The whole catalog-ci.yml workflow: triggers, top-level permissions: {}, and the build job.
templates/ci/github-verify-job.yml The verify-catalog-ci job, appended into github-ci.yml when verifyCi is true.
templates/ci/gitlab-ci.yml The whole catalog.yml job file: the catalog:build job.
templates/ci/gitlab-verify-job.yml The catalog:verify job, appended into gitlab-ci.yml when verifyCi is true.

Substitution is single-pass {{key}} replacement — a value substituted into one placeholder is never re-scanned for placeholders of its own.