Publishing to the Typst Universe¶
The Typst Universe is a git repository: typst/packages. Publishing
a package means opening a pull request that adds
packages/preview/<name>/<version>/ to it. There is no upload endpoint and no
account to register.
gotpm publish does the tedious half of that — the fork, the branch, the
correct destination directory, the commit — and hands you a gh pr create
command for the rest. gotpm never talks to GitHub itself.
One-time setup¶
Fork typst/packages on GitHub yourself; gotpm never creates a
fork. Then tell gotpm where it is:
$ gotpm config set fork.url https://github.com/you/packages
Optionally, choose where the clone of that fork lives. Unset, it is derived
from fork.url — forks/<host>/<owner>/<repo> inside gotpm's data directory:
$ gotpm config set fork.path ~/src/typst-packages
$ gotpm config list
Or set both at once in your editor with gotpm config edit.
That clone is a fork clone: gotpm's staging area, holding no work that does not exist elsewhere. Delete it whenever you like; the next publish clones it again. It is checked out sparsely, scoped to the package directory being published, so cloning the whole Universe is not the cost it sounds like.
If you publish on behalf of more than one organisation, each fork gets its own
clone, and switching fork.url is enough to switch between them: the default
location follows the url. A fork.path you set yourself does not follow it, so
switch the two together — gotpm refuses to publish into a clone of another fork
rather than pushing your submission to the wrong owner.
Publishing a version¶
From the package's working tree:
$ gotpm publish
info: committed "release: mypkg 0.1.0" on branch mypkg-0.1.0
info: pushed mypkg-0.1.0. Open a PR with:
gh pr create --repo typst/packages --base main --head you:mypkg-0.1.0 --draft --title "release: mypkg 0.1.0"
What happened, in order:
- The manifest is located, and the package root — the directory holding
typst.toml, not necessarily the one you ran the command in — becomes the source. - The fork clone is created if missing, and its
origin/mainis fetched, so a clone you made months ago does not cut a branch from a stale base. - A branch named
<name>-<version>is checked out, tracking the fork's branch of that name if it already exists. - The package files are copied into
packages/preview/<name>/<version>/, honouring.gitignoreand.typstignore, with.git,.gitignoreand.typstignorethemselves left out. - The result is committed and pushed to your fork.
Then run the gh pr create line it printed. A submission — one package
version on one branch of your fork — is as far as gotpm goes; publication is the
Universe maintainers merging it.
Reviewing before you push¶
--local stops after the commit and prints the push command instead of running
it:
$ gotpm publish --local
info: committed "release: mypkg 0.1.0" on branch mypkg-0.1.0
info: push it when you are ready:
git -C /home/you/.local/share/gotpm/forks/github.com/you/packages push origin mypkg-0.1.0
Useful the first time, when you want to see exactly what landed in the fork clone before anything becomes public.
Fix-ups¶
Publishing the same version again — because a reviewer asked for a change —
checks out the existing branch and commits on top of it, so the open pull
request updates rather than a second one appearing. For a fix-up run the commit
message is taken from your package repository's own HEAD commit, so the reason
for the change carries over instead of a second identical release: line.
Override it whenever you want:
$ gotpm publish -m "fix: entrypoint path in the manifest"
Bump instead of re-releasing
Fix-ups are for a submission that has not been merged yet. Once a version is in the Universe, its content is fixed: one coordinate names one content, for good. Publish a fix as a new version.
Generated files¶
Some files belong in the submission but not in your repository — a
thumbnail.png rendered from Typst source, say. Hooks in the manifest generate
them for the publish and clean them up after it:
[tool.gotpm]
pre-publish-hook = ["typst compile docs/thumbnail.typ thumbnail.png"]
post-publish-hook = ["rm -f thumbnail.png"]
Each is a list of shell commands, run in order from the package root. The
pre-publish hook runs before the package files are copied, and a failing command
stops the publish before anything reaches the fork clone. The post-publish hook
runs once publish is done, whether it succeeded or not, so what the pre-publish
hook left behind is always cleaned up. Commands are interpreted by a built-in
POSIX shell, so the same hook works on every platform; the programs it calls,
like typst, must still be on your $PATH. Publish prints each command, prefixed
with $, before it runs it.
Hooks are not sandboxed: they run as you, with your environment, and can do
anything you can. Review changes to [tool.gotpm] the way you would review a
script, and pass --no-hooks to publish without running either hook. Files a
hook would generate are then missing from the submission unless they already
exist.
A generated file is usually listed in .gitignore, and publish honours
.gitignore — so re-include it in .typstignore, or it never reaches the
submission:
# .typstignore
!thumbnail.png
A release checklist¶
$ gotpm bump minor # move the version in typst.toml
$ gotpm check lib.typ # every import resolves
$ gotpm install -n preview # try the Universe coordinate locally
$ gotpm publish # stage the submission and push
Do not forget the parts gotpm cannot do for you: the Universe requires a
description, a license and an authors list in the manifest, and it expects
a README.md beside it. Read the submission guidelines once
before your first package.
Requirements¶
gotpm publish is the only command that shells out to a system git binary —
it needs sparse checkout and commit signing, which the pure-Go git library
cannot do. Every other command, remote installs included, needs no git on your
$PATH.