Skip to content

The Forgejo Dialect

Forgejo (the forge behind Codeberg, self-hosted Forgejo, and Gitea) runs GitHub-Actions-compatible workflows. You author exactly as you would for GitHub Actions — same Workflow, Job, Step, and composites, imported from @intentius/chant-lexicon-forgejo instead of the github package:

import { Workflow, Job, Step, Checkout, SetupNode } from "@intentius/chant-lexicon-forgejo";
export const workflow = new Workflow({
name: "CI",
on: { push: { branches: ["main"] } },
});
export const build = new Job({
"runs-on": "ubuntu-latest",
steps: [
Checkout({}).step,
SetupNode({ nodeVersion: "22", cache: "npm" }).step,
new Step({ name: "Test", run: "npm test" }),
],
});

On build, the dialect:

  • Drops keys Forgejo ignorespermissions and continue-on-error are silently ignored by the Forgejo runner, so they are removed from the output and reported as build warnings (emitting them is misleading).
  • Maps runner labels — GitHub-hosted labels like ubuntu-latest have no fixed meaning on Forgejo. They are mapped to a default Forgejo label (docker), overridable per project. Unmapped labels pass through with a warning.
  • Resolves uses: action refs — Forgejo has no GitHub Marketplace, so a bare uses: actions/checkout@v4 is rewritten to a resolvable form. Common actions/* are mapped under an actions root (https://code.forgejo.org by default, overridable via forgejo.actionsRoot); docker/* are pinned to their full GitHub URL. Local (./...), docker://, and full-URL refs pass through untouched. Anything else passes through and is reported as a warning so it’s never silently unresolvable.

Everything else is emitted by the github serializer, which already produces the exact YAML shape Forgejo executes.

Forgejo has no lint rules or LSP completions/hover of its own — a forgejo workflow is TypeScript indistinguishable in shape from an equivalent github one, so chant lint and editor tooling run github’s checks and catalog against forgejo source unmodified (completionProvider() and hoverProvider() forward straight to githubPlugin’s). Lint rules are wrapped rather than re-exported verbatim — each GHA0xx rule appears under a WFJ- prefix (e.g. WFJ-GHA001) with its check logic untouched, so a project (or chant audit) can load the github and forgejo plugins together without an id collision. See the github lexicon’s Lint Rules page for the full GHA0xx catalog.

Because github → forgejo YAML is near-identical, the migration is thin — it applies the same dialect as a build. Its real value is the compare: what the move costs.

Terminal window
chant migrate .github/workflows/ci.yml --to forgejo -o .forgejo/workflows/ci.yml --validate

--validate prints a security posture report classifying each property’s fate across the edge:

FateMeaning
translatedcarried across as-is
approximatedcarried with a close equivalent
needs-reviewconfirm/adjust on Forgejo (unresolved uses:, unmapped runner label)
lostthe Forgejo runner ignores it (permissions, continue-on-error)

The same view is available to agents as the forgejo:compare MCP tool, which takes a workflow file and returns per-property fates plus summary counts — read-only.