ci

@ci/gitlaw (0.5.0)

Published 2026-09-16 07:24:40 +02:00 by morten-olsen

Installation

@ci:registry=
npm config set @ci:registry=
npm install @ci/gitlaw@0.5.0
"@ci/gitlaw": "0.5.0"

About this package

Gitlaw

Gitlaw reconciles a Forgejo repository with a declared configuration. It is a Bun CLI and TypeScript library: inspect the live repository, review the proposed changes, then apply only the changes needed to reach the desired state.

Forgejo is the first provider. GitHub and CI-engine configuration are not supported yet.

Install

Gitlaw is published to the internal Forgejo npm registry under @ci.

bun add --global @ci/gitlaw --registry https://code.olsen.cloud/api/packages/ci/npm/

For a persistent scoped-registry setup, add this to ~/.bunfig.toml:

[install.scopes]
"@ci" = "https://code.olsen.cloud/api/packages/ci/npm/"

Then install normally:

bun add --global @ci/gitlaw

Release binaries for Darwin and Linux, on arm64 and x64, are attached to each Forgejo release.

Authenticate

Gitlaw needs a Forgejo token with permission to read and change the resources you manage.

export FORGEJO_URL=https://code.olsen.cloud
export FORGEJO_TOKEN=...

Every command also accepts --url and --token. Do not commit tokens or place them in repository configuration.

Quick start

Start by exporting a repository's supported configuration:

gitlaw export --repo ci/gitlaw --out repo-config.yaml

Edit the resulting YAML, then preview the reconciliation:

gitlaw plan --repo ci/gitlaw --config repo-config.yaml

Apply directly when appropriate:

gitlaw apply --repo ci/gitlaw --config repo-config.yaml

For a reviewed workflow, write a plan artifact and apply that exact plan later:

gitlaw plan --repo ci/gitlaw --config repo-config.yaml --out reviewed-plan.json
gitlaw apply --plan reviewed-plan.json

apply --plan reads Forgejo again before writing. If the live repository has changed such that the reviewed operations no longer match, Gitlaw stops without making changes.

Declarative configuration

YAML configuration uses this top-level shape:

version: 1
forgejo:
  # managed settings and resources

Every configuration section is optional. An omitted section is unmanaged. A declared resource collection is owned as an exact set: Gitlaw creates missing resources, updates matching resources, and removes resources absent from that declared collection.

The complete example is in examples/repo-config.yaml.

Repository settings

forgejo:
  general:
    description: Gitlaw-managed repository
    website: https://example.invalid/project
    visibility: private # public | private
    defaultBranch: main
    template: false

  features:
    issues: true
    projects: false
    pullRequests: true
    releases: true
    wiki: false
    actions: true
    packages: true

  pullRequests:
    allowMaintainerEdits: true
    deleteBranchAfterMerge: true
    defaultUpdateStyle: rebase # merge | rebase
    ignoreWhitespaceConflicts: true
    merge:
      mergeCommit: false
      rebase: false
      rebaseWithMergeCommit: false
      rebaseAndUpdate: false
      squash: true
      fastForwardOnly: false
      default: squash

  wiki:
    branch: wiki
    allowGlobalEdits: false

  dangerZone:
    archived: false

Topics and branch protections

forgejo:
  topics: [gitlaw, forgejo]

  branchProtections:
    - name: main
      applyToAdmins: true
      requiredApprovals: 1
      enableStatusCheck: true
      statusCheckContexts: [quality, compliance]
      dismissStaleApprovals: true
      blockOnRejectedReviews: true
      requireSignedCommits: true
      enablePush: false

A branch protection is identified by name, which is the Forgejo branch-rule pattern. Gitlaw supports the Forgejo branch-protection settings exposed in the schema, including approval, merge, push, status-check, signed-commit, and file-pattern controls.

Labels

forgejo:
  labels:
    - name: bug
      color: "#d73a4a"
      description: Something is not working
      exclusive: false
      archived: false
    - name: enhancement
      color: "#a2eeef"

Labels are identified by name. Color comparisons ignore a leading # and case differences.

Webhooks

forgejo:
  webhooks:
    - type: forgejo
      url: https://automation.example/hooks/gitlaw
      contentType: json
      events: [push, pull_request]
      active: true
      branchFilter: main

Webhooks are identified by (type, url). Gitlaw supports Forgejo's webhook types and non-secret configuration: URL, content type, events, active state, and branch filter.

Do not put webhook secrets or authorization headers in YAML. Repository configuration rejects those fields.

Access

forgejo:
  access:
    collaborators:
      - username: release-bot
        permission: write # read | write | admin
    teams:
      - maintainers
    deployKeys:
      - title: production deploy
        key: ssh-ed25519 AAAA... production-deploy
        readOnly: true

Collaborators are identified by username, teams by team name, and deploy keys by title. A changed deploy key is recreated because Forgejo does not edit key material in place.

Trusted TypeScript policies

Use a TypeScript configuration when one Gitlaw-owned policy should manage several repositories or derive desired state from actual state. The configuration is executable code and must be selected explicitly:

gitlaw plan --repo ci/gitlaw --config /srv/gitlaw/gitlaw.config.ts
gitlaw apply --repo ci/gitlaw --config /srv/gitlaw/gitlaw.config.ts

Keep this file with the trusted Gitlaw policy project, not in an untrusted repository. That project should depend on @ci/gitlaw so the config can import its helpers.

import { defineConfig, pipe } from "@ci/gitlaw";

export default defineConfig({
  forgejo: {
    labels: [{ name: "bug", color: "d73a4a" }],
  },

  policies: [
    pipe(
      ({ desired }) => ({
        ...desired,
        access: {
          ...desired.access,
          collaborators: [
            ...(desired.access?.collaborators ?? []),
            { username: "contributor-bot", permission: "write" },
          ],
        },
      }),

      ({ actual, desired, addWarning, addViolation }) => {
        if (actual.general?.visibility !== "private") {
          addWarning("Repository will be made private.");
        }

        if (!desired.branchProtections?.some(({ name }) => name === "main")) {
          addViolation("main must have a branch-protection rule.");
        }
      },
    ),
  ],
});

Each policy receives:

type ForgejoPolicyContext = {
  actual: Readonly<ForgejoSettings>;
  desired: Readonly<ForgejoSettings>;
  addWarning(message: string): void;
  addViolation(message: string): void;
  addSecret(request: { type: WebhookConfig["type"]; url: string; secretRef: string }): void;
};

A policy may return updated ForgejoSettings or return nothing. pipe(...) runs policies in order and passes each policy's updated desired state to the next one.

Warnings are printed during plan and apply. Any violation stops the command before repository writes.

Webhook secrets from trusted policy code

Secrets belong outside repository configuration. A trusted TypeScript config names secret providers and policies request their use through opaque references. Gitlaw resolves a requested secret only when it writes that webhook:

import { defineConfig } from "@ci/gitlaw";

const webhookUrl = "https://automation.example/hooks/gitlaw";

export default defineConfig({
  forgejo: {
    webhooks: [{
      type: "forgejo",
      url: webhookUrl,
      contentType: "json",
    }],
  },

  secrets: {
    automationWebhook: () => {
      const secret = process.env.AUTOMATION_WEBHOOK_SECRET;
      if (secret === undefined) throw new Error("AUTOMATION_WEBHOOK_SECRET is required");
      return secret;
    },
  },

  policies: [({ addSecret }) => {
    addSecret({
      type: "forgejo",
      url: webhookUrl,
      secretRef: "automationWebhook",
    });
  }],
});

Gitlaw never writes secret values or references into exports, command output, error messages, or reviewed plan JSON. A plan records only that a secret is configured.

A reviewed plan with webhook-secret operations must be applied with the original trusted configuration so Gitlaw can resolve the secret at write time:

gitlaw plan --repo ci/gitlaw --config /srv/gitlaw/gitlaw.config.ts --out reviewed-plan.json
gitlaw apply --plan reviewed-plan.json --config /srv/gitlaw/gitlaw.config.ts

Export behavior

gitlaw export emits valid YAML for all currently managed Forgejo resources:

gitlaw export --repo ci/gitlaw

It includes repository settings, topics, protections, labels, non-secret webhooks, collaborators, teams, and deploy keys. It intentionally omits policy code and webhook-secret configuration because Forgejo cannot reveal secrets safely.

Library use

The package also exposes the configuration helpers and Forgejo reconciler:

import {
  defineConfig,
  pipe,
  ForgejoClient,
  ForgejoRepositoryReconciler,
} from "@ci/gitlaw";

The CLI is the recommended interface for normal reconciliation. Use the library when integrating Gitlaw into a trusted policy application.

Current scope

  • Forgejo repository reconciliation
  • Reviewed plans with live-state drift protection
  • YAML configuration and trusted TypeScript policies
  • Safe webhook secret resolution through trusted policy code

Not yet supported:

  • GitHub provider
  • Woodpecker or other CI-engine configuration
  • Webhook secret export or verification against remote state

Dependencies

Dependencies

ID Version
yaml ^2.8.1
zod ^4.1.12

Development dependencies

ID Version
@types/bun ^1.4.0
@types/node ^24.0.0
swagger-typescript-api ^13.12.6
typescript ^5.9.0
Details
npm
2026-09-16 07:24:40 +02:00
4
2026-10-25 20:44:51 +01:00
MIT
35 MiB
Assets (1)
Versions (8) View all
0.7.0 2026-09-16
0.6.0 2026-09-16
0.5.0 2026-09-16
0.4.1 2026-09-16
0.4.0 2026-09-16