@ci/gitlaw (0.5.0)
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 |