- Zig 82.1%
- Python 10%
- Nix 6.2%
- Shell 0.9%
- PowerShell 0.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tu1d8M5LY3LCaJ6ATanWTS |
||
| .forgejo/workflows | ||
| api | ||
| dist/windows | ||
| docs | ||
| LICENSES | ||
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| build.zig | ||
| build.zig.zon | ||
| build.zig.zon.nix | ||
| CONTRIBUTING.md | ||
| flake.lock | ||
| flake.nix | ||
| GLOSSARY.md | ||
| msi.nix | ||
| package.nix | ||
| README.md | ||
| release.nix | ||
| REUSE.toml | ||
zfj
Zig bindings for the Forgejo API. All 506 operations
and 246 models of Forgejo v16.0.3, typed, with the response of every call
parsed into the model the API documentation names for it — and zfj, a
command-line tool built on them that can perform any of those operations.
Requires Zig 0.17. The last release for Zig 0.16 is v0.1.0, and the
zig-0.16 branch carries it.
The bindings are generated, not written
This repository holds the API description — api/swagger.v1.json, taken from
the Forgejo v16.0.3 release — and a small program that turns it into Zig. The
build runs that program and compiles what it prints; no generated source is
checked in, so the bindings cannot drift from the specification they came from.
Updating to a new Forgejo release is therefore a matter of replacing one file. See Updating the specification.
zig build leaves the generated source at zig-out/api.zig, which is the
reference for what the library actually exposes. zig build docs renders the
same thing as browsable documentation.
Using it
Add the dependency:
zig fetch --save https://git.jcollie.dev/jeff/zfj/archive/main.tar.gz
then wire the module up in build.zig:
const zfj = b.dependency("zfj", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zfj", zfj.module("zfj"));
Operations are grouped the way the Forgejo API documentation groups them, one
client per tag — IssueClient, RepositoryClient, UserClient,
OrganizationClient, AdminClient, NotificationClient, PackageClient,
SettingsClient, MiscellaneousClient, ActivitypubClient:
const std = @import("std");
const zfj = @import("zfj");
pub fn main(init: std.process.Init) !u8 {
const allocator = init.gpa;
var client = zfj.Client.init(allocator, init.io, init.environ_map.get("FORGEJO_TOKEN").?);
defer client.deinit();
client.withBaseUrl("https://codeberg.org/api/v1");
var issues = zfj.IssueClient.init(&client);
var issue = try issues.issueGetIssue(.{
.owner = "forgejo",
.repo = "forgejo",
.index = 42,
});
defer issue.deinit();
std.debug.print("{s}\n", .{issue.value().title.?});
return 0;
}
Every operation is also a plain function taking the client as its first
argument, so the call above can equally be written
zfj.issueGetIssue(&client, .{ ... }). The tag clients are a grouping over
those functions and nothing more.
Three forms of each operation are generated:
| Form | Returns | For |
|---|---|---|
issueGetIssue |
Owned(Issue) |
the usual case: a parsed model, or error.ResponseError |
issueGetIssueResult |
ApiResult(Issue) |
when the failing status code matters |
issueGetIssueRaw |
RawResponse |
when the bytes matter |
Owned(T) owns both the response body and the parsed model; deinit() frees
both, and value() borrows the model out of it. Model fields are optional
and default to null, because the API omits rather than nulls what it has no
value for.
An operation's path and query values are passed as one struct, its params,
whose type is named after the operation: issueGetIssue takes an
IssueGetIssueParams. An operation that sends a body takes it as a separate
argument, typed as the model the API documentation names for it. Many of
those are Forgejo's own …Option models, such as CreateIssueOption, and
are never params.
Operation names are Forgejo's operationIds with the first letter lowercased,
so they can be looked up in the API documentation as they are. The one
exception is renderRawMarkdown, Forgejo's renderMarkdownRaw, whose id is
already the name of renderMarkdown's raw form.
Operations whose only successful response is empty — the deletes, mostly —
return !void.
RawResponse and Owned(T) both keep the response's headers, and
header(name) finds one regardless of case — which is how Forgejo says how
many results there are (X-Total-Count) and where the next page is (Link).
The specification's summaries and descriptions are doc comments on the
operations, their params and the models, so they appear in zig build docs
and in an editor. The same text is also available while a program runs:
zfj.operation_info.<operation> is an OperationInfo holding the
operation's method, path and summary, and for each of its params the
location, description and allowed values.
Authentication
The token passed to Client.init is sent as Authorization: Bearer <token>.
Forgejo accepts that spelling as well as the token <token> its documentation
describes; both are read by the same code path. Pass an empty string to make
requests unauthenticated.
The command-line tool
zig build also builds zig-out/bin/zfj. Every operation the bindings export
is a command of its own, named after the operation:
$ zfj login codeberg https://codeberg.org
Token for https://codeberg.org (made under Settings, Applications):
Logged in to codeberg (https://codeberg.org) as someone.
codeberg is the default instance.
$ zfj op issueGetIssue --owner forgejo --repo forgejo --index 42
zfj op alone lists the operations, and zfj op <operation> --help says what
an operation does, the request it makes, and each of its params with its
description and, where the API documentation gives them, the values it
accepts. The flags after an operation's name are exactly its
params, spelled as the API documentation spells them — --page, --type,
--color and all. The tool's own options, such as --instance, --debug and
--color for the terminal, go before the command and nowhere else, so the
two can never be confused.
An operation that sends a request body reads it from stdin as JSON, and the JSON is checked against the request body's model before anything is sent, so a misspelled field is caught locally:
$ echo '{"title": "It broke"}' | zfj op issueCreateIssue --owner me --repo thing
An operation that uploads a file takes it as a path, and sends it under the
path's own name; - reads it from stdin instead, named by --name:
$ zfj op issueCreateIssueAttachment --owner me --repo thing --index 42 --attachment crash.log
A list operation returns one page at a time. With --paginate, zfj follows
the Link header Forgejo sends from page to page and writes every page's items
as one JSON array:
$ zfj --paginate op issueListIssues --owner forgejo --repo forgejo --state all
A successful response is written to stdout exactly as Forgejo sent it. It is
also parsed into the model the API documentation names for it; if that fails,
zfj warns on stderr, and with --strict it fails instead. An error response
goes to stderr, so stdout only ever holds a successful one.
| Exit status | Meaning |
|---|---|
| 0 | success |
| 1 | the instance answered with an error status |
| 2 | a usage error: an unknown option, a missing param, a request body that does not parse |
| 3 | no instance to reach, the configuration file cannot be read, or the request got no answer |
| 4 | with --strict, a successful response that does not parse into its model |
| 5 | with --exit-status, a run that did not succeed |
A reader that stops early, such as head in zfj run view 12 --log | head,
is not an error: zfj stops writing and exits 0, saying nothing on stderr.
Working with issues
zfj issue holds commands shaped around tasks rather than single operations,
spelled as fj's are:
$ zfj issue list --state all -l bug
$ zfj issue view 42 --comments
$ zfj issue view 42 --web
$ zfj issue create -t "It broke" -F report.md -l bug
$ zfj issue comment 42 -b "Fixed in 1a2b3c4."
$ zfj issue edit 42 --add-label needs-info --add-assignee someone
$ zfj issue close 42 -c "Fixed in 1a2b3c4."
$ zfj issue reopen 42 -c "Still happens on Windows."
$ zfj issue block 42 --by 41
$ zfj issue blockers 42
The repository is named with -R <owner>/<repo>. Without it, it is read off
the clone's upstream remote, or failing that its origin, and the remote
has to be on the instance being reached — which, unless --instance or
ZFJ_INSTANCE names another, is the configured instance on the remote's
host. Without -b or -F, and on a terminal, create and comment open
$EDITOR for the text. close and reopen take a comment with -c or -F,
post it before changing the issue's state, and without either leave no
comment. view --web opens the issue in $BROWSER when that is set, and
otherwise in the user's chosen browser, through
zig-openuri: the desktop portal
on Linux, falling back to xdg-open where there is no portal (over ssh, say),
and ShellExecuteExW on Windows. On macOS it runs open.
On a terminal, lists are columns and an issue is shown with its body as
Markdown, through $ZFJ_PAGER, $PAGER or less (on Windows, only
$ZFJ_PAGER). The editor is $VISUAL or $EDITOR, else vi (notepad on
Windows). Piped elsewhere, a list is
one issue to a line with its number, state, title and labels separated by
tabs. --json writes Forgejo's own JSON instead; view --comments --json
adds the comment thread to the issue as a comments list, and list --json
honors --paginate. zfj issue <command> --help says what each takes.
Pull requests
zfj pr opens them and reads them:
$ git push origin my-branch
$ zfj pr create -t "Fix the thing" -b "It was broken." -l bug
$ zfj pr list --state all -B main
$ zfj pr view 42 --comments
$ zfj pr diff 42
$ zfj pr diff 42 --patch | git am
create opens a pull request from a branch already pushed. The head is the
branch checked out unless -H names another, and the base is the
repository's default branch unless -B names another. In a clone of a fork,
where origin is the fork and upstream the repository it came from, the
pull request goes to upstream from the branch on origin. Without -b or
-F, and on a terminal, $EDITOR is opened for the description.
list filters by -l <label>, -A <author>, -B <base branch> and
-H <head branch>, and shows a merged pull request as merged where Forgejo
says only closed; piped elsewhere it is one to a line with its number,
state, title, head and base branches, and labels separated by tabs. view
takes --comments, --json and --web as issue view does. diff writes
the unified diff, or with --patch the commits for git am. A pull request
is numbered from the same sequence as issues, so commenting on one, labeling
it and closing it are the zfj issue commands.
Runs
zfj run follows the runs of a repository's
Forgejo Actions workflows,
spelled as gh run's are:
$ zfj run list
$ zfj run list -w release.yaml -s failure
$ zfj run list -c HEAD
$ zfj run view 43
$ zfj run view 43 --exit-status && echo passed
$ zfj run view 43 --log-failed
$ zfj run view 43 --log -j build --attempt 1
$ zfj run watch 44 --exit-status
$ zfj run cancel 44
$ zfj run delete 44
list shows the newest runs first, from every branch, twenty to a page. It
filters by -s <status> and -e <event> (each repeatable),
-w <workflow file>, -b <branch> and -c <commit>. Inside a clone, -c
takes anything git can resolve to a commit, such as HEAD or a short hash;
outside one, it has to be the full hash. On a terminal it shows columns. Piped
elsewhere, it writes one run per line with its run number, status, title,
workflow, event, branch or tag, and start time, separated by tabs. --json
writes Forgejo's JSON for the runs and honors --paginate. --web opens the
repository's runs in the browser instead, where the only filters are the
workflow and a single status.
view shows a run's title, status, workflow, event, branch or tag, and
commit; when and by whom it was started and how long it took; and its jobs,
each with its status and attempt number. --json writes Forgejo's JSON for
the run with its jobs added as a jobs list, and --web opens the run in the
browser.
view --log shows each job's log in place of the summary: the latest attempt,
under a line naming the job, and as far as the job has got if it is still
running. A job no runner has picked up yet is listed with no log yet.
--log-failed shows only the jobs that failed. -j limits either one to a
single job, named by its name, or by its ID when two jobs share a name.
--attempt <n>, which needs -j, shows an earlier attempt instead of the
latest.
watch follows a run until it finishes, checking every three seconds, or
every -i <seconds>. On a terminal it redraws the run and its jobs in place.
Piped elsewhere, it writes one line each time the run or a job changes
status, such as run 44: running or job build: failure. A run that has
already finished is shown once.
view and watch take --exit-status, which makes zfj exit with status 5
unless the run succeeded. A run that hasn't finished yet hasn't succeeded
either.
cancel cancels whatever jobs of a run are still waiting or running, and
says so when the run had already finished, since Forgejo answers the same
way either way. delete deletes a run that has finished. Forgejo refuses
to delete one that hasn't, so cancel it first. Neither command asks before
acting.
zfj workflow run starts a run of a workflow whose on includes
workflow_dispatch, on the repository's default branch unless --ref names
another branch or tag. Each -f <key>=<value> fills one of the workflow's
inputs. It prints the new run's number and where to see it; --json writes
what Forgejo answered instead. Forgejo's API has no way to list a
repository's workflows, so run is the only workflow command.
$ zfj workflow run deploy.yaml -f environment=staging
Started run 44 of deploy.yaml on main
https://git.jcollie.dev/jeff/thing/actions/runs/44
A run is named by its run number, the one the web interface shows in
/actions/runs/43, rather than by the ID the API's operations take. A number
the repository doesn't have exits with status 1. See
ADR 0005.
Working with labels
zfj label lists a repository's labels and creates them:
$ zfj label list
$ zfj label create needs-triage -c d4c5f9 -d "Maintainer needs to evaluate this issue"
$ zfj label create needs-triage -c d4c5f9 -d "Waiting to be looked at" --force
Forgejo lets a repository hold two labels of the same name, so create
looks for the name first and refuses one that already exists; --force
updates that label's color and description instead, which makes a script
setting up a repository's labels safe to run again. A color is six hex
digits, with or without a leading #. list always reads every page; on a
terminal it shows columns, and piped elsewhere one label to a line with its
name, color and description separated by tabs. --json writes Forgejo's
JSON, and -R names the repository as it does for zfj issue.
Repositories
zfj repo works with repositories themselves:
$ zfj repo view
$ zfj repo view forgejo/forgejo
$ zfj repo view thing --web
$ zfj repo list
$ zfj repo list forgejo
$ zfj repo clone forgejo/forgejo
$ zfj repo clone thing ~/src/thing -p ssh -- --depth 1
$ zfj repo create thing -d "A thing" --license MIT --gitignore Zig --clone
$ zfj repo create my-org/thing --private
$ zfj repo fork forgejo/forgejo --clone
$ zfj repo fork --remote
$ zfj repo edit -d "A better thing" --website https://thing.example
$ zfj repo edit me/thing --archive --disable wiki,projects
$ zfj repo delete me/thing
Here the repository is what the command acts on, so it's the argument rather
than -R: <owner>/<repo>, or a bare <repo> for one of the authenticated
user's own, or nothing for the one the clone's upstream or origin remote
names. See ADR 0006.
view shows whether the repository is public, private or internal, whether
it's a fork and of what, a mirror, a template or archived, its description,
default branch and website, its stars, forks, open issues and open pull
requests, and its https and ssh clone URLs. --json writes Forgejo's JSON,
and --web opens the repository in the browser.
list alone lists the authenticated user's repositories and those of the
organizations they belong to; list <owner> lists a user's or an
organization's. It honors -L and --paginate as issue list does; piped
elsewhere it is one repository to a line with its full name, visibility,
what else it is (fork, mirror, template, archived, separated by
commas) and description, separated by tabs.
clone runs git clone, handing it whatever follows a --. A fork's clone
is given the fork's parent as its upstream remote, which is where zfj's
issue and pull request commands go from a clone that has one. It clones over
https, or over ssh with -p ssh; zfj login <name> <url> --protocol ssh
makes ssh the instance's default, and logging in again without --protocol
keeps whichever was chosen.
create <name> creates a repository of the authenticated user's own, and
create <owner>/<name> one in that organization. It takes -d for a
description, --private, --template, --default-branch, and
--label-set for a set of issue labels such as Default. --init makes a
first commit with a README; --readme, --license and --gitignore (commas
separate several) choose the templates for it and imply --init. It prints
the new repository's address, or with --json Forgejo's JSON. Nothing local
changes unless --clone clones the new repository, or --remote adds it
to the clone in the working directory as origin. Nothing is pushed.
fork forks a repository to the authenticated user, or with --org to an
organization, and --name names the fork otherwise. A fork already there is
no failure: it is found, however it is named, and used instead, so forking
twice is the same as forking once. It prints the fork's address. Nothing
local changes unless --clone clones the fork, with the repository as
upstream, or --remote, in a clone of the repository, renames its origin
to upstream and adds the fork as origin. Either way the clone's issue and
pull request commands still go to the repository, and pr create opens pull
requests to it from the fork.
edit changes only what it is given: --name renames the repository,
-d, --website and --default-branch set those, --private or
--public, --archive or --unarchive, and --template or
--no-template set its state, and --enable and --disable turn its units
on and off — issues, wiki, pull-requests, projects, releases,
packages and actions, with commas separating several. The merge settings
and the rest of what Forgejo can change are for
zfj op repoEdit. A rename leaves the clone's remotes alone, since Forgejo
redirects the old name, and says how to point them at the new one. edit
names the repository as <owner>/<repo> or takes the clone's remote, but
never takes a bare <repo>, so it cannot change one of the wrong owner's.
delete takes the repository only in full, as <owner>/<repo> — never a
bare name, and never from the clone's remote — so that running it in the
wrong directory cannot be enough to delete anything. On a terminal it asks
for the full name to be typed back, and deletes nothing unless it matches
exactly; anywhere else it deletes nothing without --yes, which also skips
the question on a terminal.
Instances
zfj login <name> <url> asks for a token — or reads it from stdin, when that
is not a terminal — checks it by asking the instance whose it is, and keeps
it. zfj instances lists what it has kept, zfj default <name> chooses the
instance reached when no other is named, and zfj logout <name> forgets one.
They are kept in $XDG_CONFIG_HOME/zfj/config.zon — or
~/.config/zfj/config.zon, or on Windows %APPDATA%\zfj\config.zon — which
holds the tokens, and so on Unix is written readable by its owner alone.
A command reaches the instance named by --instance, or failing that by
ZFJ_INSTANCE, or failing that the configured instance on the host of the
clone's git remote, or failing that the default. Either may be a name or a URL; a
URL need not be configured, and one that is not is reached anonymously.
FORGEJO_TOKEN, when it is set, is used in place of the chosen instance's
token.
zfj --version names the tool's version and the Forgejo release its API
description came from — zfj 0.2.0 (Forgejo 16.0.3). The bindings carry the
latter as zfj.forgejo_version.
--debug logs each request and the status of its response to stderr, and
never logs headers, so the token cannot appear there.
Shell completion
zfj completion <shell> prints a completion script for fish, bash, zsh or
PowerShell:
$ zfj completion fish | source
$ zfj completion fish > ~/.config/fish/completions/zfj.fish
$ source <(zfj completion bash)
$ zfj completion bash > ~/.local/share/bash-completion/completions/zfj
$ source <(zfj completion zsh)
$ zfj completion zsh > "${fpath[1]}/_zfj"
PowerShell, Windows PowerShell 5.1 as well as PowerShell 7, loads it from
$PROFILE:
zfj completion powershell | Out-String | Invoke-Expression
zsh's script is loaded after compinit, or found on $fpath as _zfj.
Installed with Nix, zfj's package puts the fish, bash and zsh scripts where
those shells look for them, and nothing needs loading by hand. Each release
archive carries all four scripts in completions/, and the Windows installer
puts zfj.ps1 beside zfj.exe, so that $PROFILE can load it as
. "$env:ProgramFiles\zfj\zfj.ps1" rather than run zfj on every start.
bash shows no descriptions beside the candidates; fish and zsh do, and
PowerShell shows them as tooltips.
The script asks zfj itself what can come next, so it completes every
command, option and option value zfj has, the operations of zfj op with
their summaries, and those operations' params, and stays right as zfj
changes. Global options are offered only before the command, where zfj
accepts them. It also completes what zfj can find out without asking the
instance: the configured instances' names for --instance, logout and
default, the workflow files of the clone for workflow run and
run list -w, files for -F and for an operation's file uploads, and
directories for repo clone. Nothing is fetched from the instance while
completing, so issue numbers, labels and the like are not completed. See
ADR 0007.
Installing on Windows
Each release has an installer, zfj-<version>-x86_64-windows.msi, which
installs zfj.exe for all users into Program Files\zfj and puts that
directory on the PATH, so zfj runs from any newly opened shell. Installing
a later release replaces the earlier one, and Apps & Features removes it. It
is an x64 installer, and Windows on Arm runs it under emulation; the
aarch64-windows archive below is the native build.
The same installer is in the JCOllie winget source, which is signed with a
self-signed certificate. From an elevated PowerShell, this trusts that
certificate and adds the source, once:
irm https://winget.jcollie.dev/install.ps1 | iex
after which zfj installs and upgrades like any other package:
winget install -s JCOllie JCOllie.Zfj
winget upgrade JCOllie.Zfj
jeff/winget-pkgs holds the
source's manifests and says more about it.
Downloading a release
Each release on git.jcollie.dev
carries a ready-built zfj for four targets:
| Archive | Runs on |
|---|---|
zfj-<version>-x86_64-windows.zip |
64-bit Windows on x86 |
zfj-<version>-aarch64-windows.zip |
64-bit Windows on Arm |
zfj-<version>-x86_64-linux-musl.tar.gz |
Linux on x86-64, any distribution |
zfj-<version>-aarch64-linux-musl.tar.gz |
Linux on 64-bit Arm, any distribution |
The Linux binaries are statically linked against musl and need nothing from
the system. Each archive unpacks into one directory, zfj-<version>-<target>,
holding the executable, this README, and the license. SHA256SUMS beside them
lists the checksum of each archive and of the installer; with any of them
downloaded into the same directory,
$ sha256sum -c --ignore-missing SHA256SUMS
checks whichever of them are there.
Where this lives
The repository's home is git.jcollie.dev, with a mirror on Tangled at https://tangled.org/jcollie.dev/zfj:
$ git clone https://git.jcollie.dev/jeff/zfj.git
Reporting issues and contributing
Report problems and send pull requests on the Tangled mirror, https://tangled.org/jcollie.dev/zfj. CONTRIBUTING.md says what a change needs before it goes in.
Cloning with Radicle
The repository is also published on the Radicle peer-to-peer network, where a repository is found by its Repository ID rather than by a server name:
rad:z2LTU53rqByAo7pj2BM1vJHeKYJSb
With a local Radicle node running, clone it with:
$ rad clone rad:z2LTU53rqByAo7pj2BM1vJHeKYJSb
That fetches the repository, checks out the default branch, and starts seeding
it so other peers can fetch from you. To follow the repository without checking
out a working copy, use rad seed instead:
$ rad seed rad:z2LTU53rqByAo7pj2BM1vJHeKYJSb
If you already have a clone from the git remote above, you can attach it to the same Radicle repository rather than cloning again:
$ git remote add rad rad://z2LTU53rqByAo7pj2BM1vJHeKYJSb
$ git fetch rad
Updating the specification
$ zig build update-spec
fetches Forgejo's latest release and rewrites api/swagger.v1.json from it.
To take a particular release instead — an older one, or a newer one that is
not yet the latest across all of Forgejo's parallel release branches — name
its tag:
$ zig build update-spec -Dforgejo-version=v16.0.3
Then run zig build test. Nothing else in the repository names a Forgejo
version, so that is the whole of it.
The step is the only thing here that reaches the network, and nothing else
depends on it: an ordinary build never runs it. What it fetches is
templates/swagger/v1_json.tmpl from the release tag, which is the document
an instance renders and serves at /swagger.v1.json. Taking it from the tag
rather than from a running instance is what makes the vendored file say which
Forgejo it describes — an instance's copy is stamped with whatever build
happens to be deployed there.
The template is plain JSON but for two Go placeholders, the instance's version and its sub-URL, and both are required to be present. If a future Forgejo renames one or adds a third, the step fails rather than vendoring a file with a template expression left in the middle of it. What it fetched is also parsed and checked over — that it is JSON, that it is Swagger 2.0, that its version is the release asked for, and that it describes any paths at all — before it is allowed to replace the file in the source tree.
Building with Nix
$ nix build .#zfj
builds the bindings and the command-line tool and runs the tests without
touching the network, leaving zfj in result/bin/ and the generated
api.zig in result/. The Zig dependencies come from
build.zig.zon.nix, which zon2nix
generates from build.zig.zon; regenerate it whenever a dependency changes:
$ nix develop -c zon2nix --17 --nix=build.zig.zon.nix build.zig.zon
The same dependencies are exposed on their own as .#zig-deps, for handing to
zig build --system. openapi2zig declares a .path dependency of its own,
which --system cannot build from the store, so package.nix forks it from a
copy inside the build root, as the zon2nix README describes.
nix run .# -- <arguments> runs the tool.
The release builds are packages too: .#zfj-x86_64-windows,
.#zfj-aarch64-windows, .#zfj-x86_64-linux-musl and
.#zfj-aarch64-linux-musl cross-build the stripped executable, and
.#msi builds the Windows installer, with msitools' wixl from
dist/windows/zfj.wxs, and .#release gathers everything a release
publishes: the four archives, the installer, and SHA256SUMS. On x86-64 Linux,
$ nix build -L .#checks.x86_64-linux.windows
runs the Windows executable under Wine and checks that it answers the way the
native build does and finds its configuration under %APPDATA%, then installs
the MSI with msiexec, runs the installed zfj.exe, and uninstalls it. It
does not reach the network, because Wine leaves out a structure that Zig's
standard library reads while resolving a host name.
$ nix build -L .#checks.x86_64-linux.forgejo
runs the tool against a real Forgejo in a NixOS virtual machine — logging in,
plumbing calls checked against their documented models, pagination, and every
zfj issue command — and needs KVM.
References cited
- Forgejo. Forgejo API description (
templates/swagger/v1_json.tmpl), v16.0.3. https://codeberg.org/forgejo/forgejo/src/tag/v16.0.3/templates/swagger/v1_json.tmpl. - OpenAPI Initiative. OpenAPI Specification Version 2.0. September 2014. https://spec.openapis.org/oas/v2.0.html.
- Nottingham, M. Web Linking. RFC 8288, RFC Editor, October 2017. https://www.rfc-editor.org/info/rfc8288.
- freedesktop.org. XDG Base Directory Specification. https://specifications.freedesktop.org/basedir-spec/latest/.
- Git. “git-clone Documentation: GIT URLS.” https://git-scm.com/docs/git-clone#_git_urls.
License
MIT, and the repository follows the REUSE
specification — reuse lint checks it. api/swagger.v1.json is Forgejo's own,
distributed by them under the MIT license for the purpose of interoperability.