Skip to main content

Working with catladder

catladder generates this repository's CI/CD pipelines, deployments and environment files from the TypeScript config catladder.ts at the repo root. It is installed per project (@catladder/cli) — always use the locally installed version (yarn catenv, yarn catladder).

Golden rules​

  1. Never hand-edit generated files. Everything under .catladder-generated/, the generated parts of .gitlab-ci.yml, and catladder-generated workflows under .github/workflows/ are overwritten on every regeneration (they carry a "generated by catladder" header). To change them, change catladder.ts and regenerate.
  2. Regenerate after every catladder.ts change by running yarn catenv. With direnv set up, re-entering the project directory does this automatically.
  3. Commit the regenerated files together with the config change. Generated files are checked in; CI includes them directly.

Structure of catladder.ts​

import type { Config } from "@catladder/cli";

const config = {
appName: "my-app",
customerName: "pan",
pipelines: { gitlab: true, github: true }, // which CI systems to generate
environments: {
// optional: project-wide env declarations/tuning
review: { autoStop: "3 days" },
stage: false, // the project has no stage env (any component)
next: { type: "dev", on: { branch: "next" } }, // extra branch-tracking env
},
components: {
www: {
dir: "frontend", // working directory of this component
vars: { public: {}, secret: [] }, // env vars (see catladder-secrets skill)
build: { type: "node" }, // node | rails | meteor | custom — see catladder-builds
deploy: { type: "kubernetes" }, // kubernetes | google-cloudrun | dockerTag | custom — see catladder-deploys
env: {
// enable/customize per environment
review: {},
dev: {},
stage: false, // false disables the env for this component only
prod: {},
},
},
},
} satisfies Config;

export default config;

Key concepts:

  • Components are the sub-apps/services of the project (frontend, api, ...). Each has a build config and a deploy config.
  • Environments: dev (deployed on push to the main branch), review (per merge/pull request), stage and prod (deployed on tagged releases), local (only for local development via catenv). Environments are project-wide: the top-level environments config declares and tunes them consistently for all components, while per-component env.<name> entries only carry component-local overrides (vars, deploy settings, host, autoStop) or false to opt the component out. In environments:
    • <name>: false — disables the env for the whole project: no component has it, so no setup contexts (catladder project setup), secrets, pipeline jobs/workflows or catenv for it. This is how to say "this project has no stage": environments: { stage: false } (not on: false). local cannot be disabled; inheriting from a disabled env is an error.
    • on — when the env deploys: "mainBranch", "mr", "taggedRelease", { branch: "next" } (a stable env deploying on pushes to that branch), or false (never deployed by a pipeline — the env still exists for setup, secrets and catenv; use environments.<name>: false to remove it). on: "mr" makes an env a review app (one instance per MR/PR, torn down on close). Defaults from the env type.
    • autoStop — how long a stoppable environment stays up after its last deploy before gitlab stops it (e.g. "3 days"; false disables). Defaults: review "1 week", dev "4 weeks", others never. GitLab only — github review apps stop when their PR closes. Components can override it per env (env.<name>.autoStop).
    • Extra envs are declared with any name plus a type — the env type acts as a preset: it supplies all defaults (trigger, autoStop, deploy policy) and the env's identity for tooling (ENV_TYPE), everything overridable. Every component deploys to a declared env unless it opts out: environments: { next: { type: "dev", on: { branch: "next" } } }.
    • inherit — make an env inherit from another env: inherit: "dev" gives it dev's per-component config overrides (merged below its own; host and type never inherit, the env type is implied from the inherited env) AND dev's secret values — all-or-nothing: its jobs reference dev's CL_DEV_* variables and it has no secret store of its own (manage secrets via dev; rotating affects both). The object form separates the axes: inherit: { config: "dev" } inherits config only, inherit: { config: "dev", secrets: "dev" } both.
  • Review apps: top-level reviewApps: { deploy, label, skipLabel, drafts } decides whether merge/pull requests deploy their review apps — every MR/PR (default), opt-in or opt-out by label, or on request; and what drafts run. See the catladder-pipelines skill.
  • Pipelines: pipelines: { gitlab: true, github: true } selects which CI systems get generated files. Options objects instead of true allow per-pipeline settings (e.g. runnerVariables, gitRemote, and repository — GitHub only, for lowercasing GHCR image paths; see the catladder-pipelines skill).
  • Package manager: yarn and pnpm are supported and autodetected (from the packageManager field in package.json or the lockfile; yarn is the fallback). packageManager: "pnpm" | "yarn" at the top level overrides the detection. In pnpm projects, run the CLIs as pnpm catenv / pnpm catladder.

Common tasks​

  • Add a component: add an entry under components, run yarn catenv, commit config + generated files.
  • Change resources / deployment settings: edit the component's deploy (or per-env override), regenerate.
  • Inspect the resulting pipeline: read the generated files (e.g. .catladder-generated/gitlab/, .github/workflows/) — but never edit them.

Escape hatches — last resort, and a signal to file an issue​

catladder has two escape hatches for things it doesn't (yet) support natively:

  • customJobs (per component) — hand-written CI job specs injected into the pipeline.
  • transform hooks (top-level hooks) — transformFileBeforeWrite / transformYamlBeforeWrite rewrite the generated files just before they're written to disk.

Both work, but they are fragile by design: they depend on the exact shape of catladder's generated output, so a catladder upgrade can break them silently, and they sit outside everything the rest of the config gives you (rules, caching, images, per-env logic, snapshots). Prefer a first-class config option whenever one exists — check the build, deploy, releases, and env/vars surfaces (and the sibling skills) first.

When you do reach for one, treat it as a missing-feature signal. If a dev needs customJobs or a transform hook, it almost always means catladder is missing a capability that other projects will need too. Open an issue at https://github.com/panter/catladder/issues describing the use case (what you're trying to achieve and why the existing config can't express it) so it can be generalized into a supported feature — and, where possible, offer to help the dev write that issue. The goal is for escape hatches to shrink over time, not accumulate.

  • catladder-builds — the component build config (build types, Docker)
  • catladder-deploys — the component deploy config (k8s, Cloud Run, …)
  • catladder-secrets — env vars and secret management
  • catladder-releases — releasing (semantic-release / changesets)
  • catladder-pipelines — how the generated pipelines work, debugging CI
  • catladder-cli — non-interactive CLI usage and command reference
  • catladder-migrate-ci-backend — moving a project between GitLab CI and GitHub Actions
  • catladder-migrate-release-method — switching between semantic-release and changesets
  • catladder-migrate-package-manager — switching a project between yarn and pnpm