Release recipes

These recipes start after your build has produced release files. Each shows the expected layout and a complete release.toml for those files. Replace the example project, version, URLs, and paths with your own.

Save one recipe as release.toml, then create the bundle in a CI job with a supported OIDC identity:

packslip create --manifest release.toml --out packslip

For local key signing, add --key release.key. Upload both the artifacts and the bundle. The CLI does not upload them. On GitHub, you can instead pass manifest: release.toml to the action , along with an artifacts input matching the release files.

Rust CLI with bundled documentation

This layout ships the executable, a static zsh completion, and a man page in one archive. Generate the documentation in your existing build job. All resource paths include the archive’s top-level directory.

dist/mytool-1.2.3-linux-x64.tar.gz
└── mytool-1.2.3/
    ├── bin/mytool
    └── share/
        ├── zsh/site-functions/_mytool
        └── man/man1/mytool.1
project = "github.com/owner/mytool"
version = "1.2.3"
url_base = "https://github.com/owner/mytool/releases/download/v1.2.3"

[source]
repo = "https://github.com/owner/mytool"
tag = "v1.2.3"

[[artifact]]
path = "dist/mytool-1.2.3-linux-x64.tar.gz"
bin = ["mytool"]

[[resource]]
kind = "completion"
shell = "zsh"
bin = "mytool"
archive = "mytool-1.2.3/share/zsh/site-functions/_mytool"

[[resource]]
kind = "man"
bin = "mytool"
archive = "mytool-1.2.3/share/man/man1/mytool.1"

bin = ["mytool"] finds the executable and records its full archive path. Resources use explicit paths. If you add archives with different layouts, scope the resources .

Go CLI with generated completions

This example assumes the tool implements mytool completion SHELL, printing a completion script to stdout. Adapt exec to the interface your program actually supports; packslip does not add that command.

dist/mytool-1.2.3-linux-x64.tar.gz
└── mytool
project = "github.com/owner/mytool"
version = "1.2.3"
url_base = "https://github.com/owner/mytool/releases/download/v1.2.3"

[source]
repo = "https://github.com/owner/mytool"
tag = "v1.2.3"

[[artifact]]
path = "dist/mytool-1.2.3-linux-x64.tar.gz"
bin = ["mytool"]

[[resource]]
kind = "completion"
bin = "mytool"
shells = ["bash", "zsh", "fish"]
exec = ["mytool", "completion", "{shell}"]

The consumer substitutes the requested shell and caches successful output for the installed version, executable, and shell. Creation records this command; it does not run it. To avoid executing the binary to generate completions, ship static scripts or a usage spec .

Monorepo tool with an executable alias

A repository can release tools independently or attach several tools to one release. Give each tool its own project subpath and manifest, and include only that tool’s artifacts. This example exposes lint-x86_64 as the command lint.

dist/lint-1.2.3-linux-x64.tar.gz
└── bin/lint-x86_64
project = "github.com/owner/toolkit/lint"
version = "1.2.3"
url_base = "https://github.com/owner/toolkit/releases/download/lint-v1.2.3"

[source]
repo = "https://github.com/owner/toolkit"
tag = "lint-v1.2.3"

[[artifact]]
path = "dist/lint-1.2.3-linux-x64.tar.gz"
bin = [{ name = "lint", path = "bin/lint-x86_64" }]

The output is packslip.lint.sigstore.json; its signer still belongs to owner/toolkit. Run creation separately for other tools. When several tools share a release, use that release’s tag and download URL for each manifest. A supplementary release list can map a shared tag to each tool’s version when the tag itself does not do so.

Desktop application for Linux and macOS

The Linux archive contains a runnable command and desktop integration files. The macOS zip contains an application bundle without a PATH command. Exact artifact scope prevents either platform from receiving the other’s resources.

dist/myapp-1.2.3-linux-x64.tar.gz
├── bin/myapp
└── share/
    ├── applications/myapp.desktop
    └── icons/hicolor/256x256/apps/myapp.png

dist/myapp-1.2.3-darwin-arm64.zip
└── MyApp.app/
    └── Contents/…
project = "github.com/owner/myapp"
version = "1.2.3"
url_base = "https://github.com/owner/myapp/releases/download/v1.2.3"

[source]
repo = "https://github.com/owner/myapp"
tag = "v1.2.3"

[[artifact]]
path = "dist/myapp-1.2.3-linux-x64.tar.gz"
bin = ["bin/myapp"]

[[artifact]]
path = "dist/myapp-1.2.3-darwin-arm64.zip"
bin = []

[[resource]]
kind = "desktop"
artifact = "myapp-1.2.3-linux-x64.tar.gz"
archive = "share/applications/myapp.desktop"

[[resource]]
kind = "icon"
artifact = "myapp-1.2.3-linux-x64.tar.gz"
archive = "share/icons/hicolor/256x256/apps/myapp.png"

[[resource]]
kind = "app"
artifact = "myapp-1.2.3-darwin-arm64.zip"
archive = "MyApp.app"

Consumers choose which resource kinds they support. An app-aware consumer can install the application bundle; a CLI-only consumer is not required to do so. A packslip signature does not replace platform code signing or notarization.

Add build provenance

These manifests describe release contents. They do not generate build provenance. The GitHub Action can attest its matched files and link those statements. With the CLI, pass a provenance URL for each artifact:

--provenance FILENAME=URL

Consumers verify provenance separately from the packslip signature.