ci

@ci/gitlaw (0.7.0)

Published 2026-09-16 08:01:51 +02:00 by morten-olsen

Installation

@ci:registry=
npm config set @ci:registry=
npm install @ci/gitlaw@0.7.0
"@ci/gitlaw": "0.7.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. Supplying the same YAML and optional trusted policies re-evaluates them and requires the resulting desired state to match the reviewed plan:

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

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 credentials, including secret fields or authorization headers, in YAML. Repository configuration rejects them.

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

Repository desired state is always YAML. A trusted TypeScript policy module is a separate, optional input: it evaluates that YAML desired state, can derive policy adjustments from live state, and can report warnings, violations, or in-memory secrets. It must live with Gitlaw-owned policy code—not inside an untrusted repository.

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

The policy project should depend on @ci/gitlaw. Its default export is definePolicies(...); it never includes a forgejo section. See examples/gitlaw-policies.ts.

import { compose, definePolicies } from "@ci/gitlaw";

export default definePolicies({
  policies: [
    compose(
      ({ 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>;
  secretRequests: Readonly<Record<string, unknown>>;
  addWarning(message: string): void;
  addViolation(message: string): void;
  addSecret(name: string, value: string): void;
};

A policy may return updated ForgejoSettings or return nothing. compose(...) 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.

Generic secrets from trusted policy code

Secrets belong outside repository YAML. A trusted policy module can provide policy-specific request context, and policies can collect named non-empty string values for the active reconciliation:

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

export default definePolicies({
  secretRequests: {
    deployment: { environment: "production" },
  },
  policies: [({ secretRequests, addSecret }) => {
    const deployment = secretRequests.deployment as { environment: string };
    const token = process.env.DEPLOYMENT_TOKEN;
    if (deployment.environment === "production" && token !== undefined) {
      addSecret("deployment-token", token);
    }
  }],
});

The generic core keeps request context and collected values in memory only for that reconciliation, and passes the collected secret bag to its reconciler. It is not serialized, logged, exported, added to reviewed plans or Forgejo operations, or included in errors. Current Forgejo reconciliation accepts the bag but does not write webhook, repository, or organization secrets; a future Woodpecker configuration target will consume the same request/bag model.

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 trusted TypeScript policy configuration.

Library use

The package also exposes the configuration helpers and Forgejo reconciler:

import {
  definePolicies,
  compose,
  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
  • Generic in-memory secret collection for trusted TypeScript policies

Not yet supported:

  • GitHub provider
  • Woodpecker or other CI-engine configuration
  • Secret-provider integrations, including webhook, Forgejo Actions, and Woodpecker secret management

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 08:01:51 +02:00
4
2026-10-25 20:44:51 +01:00
MIT
latest
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