Create a GitHub Release and upload archive assets to it.
Tinfoil's own replacement for softprops/action-gh-release in the
generated workflow. It uses Req to
talk to GitHub's REST API directly, so the release lifecycle
(create → upload assets → handle existing releases) happens inside
the tool rather than inside CI-specific third-party actions.
The generated workflow calls this module via mix tinfoil.publish
once, after the build matrix finishes, on a single ubuntu-latest
runner.
Homebrew formula publishing is handled separately by
Tinfoil.Homebrew -- the generated workflow runs
mix tinfoil.homebrew in a follow-up job once the GitHub Release
exists.
Summary
Types
Known error atoms returned by publish/2. Callers can pattern-match
on these; the catch-all is {:error, term()} for unexpected failures.
Functions
Publish release archives from input_dir (default "artifacts") to
a new GitHub Release on the repo configured in the tinfoil config.
Types
@type error() :: :missing_github_token | :missing_tag | :release_already_exists_no_replace | :release_not_found_for_attach | :attach_and_replace | {:missing_input_dir, Path.t()} | {:create_release_failed, non_neg_integer(), term()} | {:find_release_failed, non_neg_integer(), term()} | {:delete_release_failed, non_neg_integer(), term()} | {:upload_failed, String.t(), non_neg_integer(), term()} | {:create_release_error, term()} | {:find_release_error, term()} | {:delete_release_error, term()} | {:upload_error, String.t(), term()} | String.t()
Known error atoms returned by publish/2. Callers can pattern-match
on these; the catch-all is {:error, term()} for unexpected failures.
:missing_github_token-- noGITHUB_TOKEN/GH_TOKENenv var:missing_tag-- no tag given andGITHUB_REF_NAMEis unset:release_already_exists_no_replace-- release for the tag already exists and neitherattach: truenorreplace: truewas passed:release_not_found_for_attach--attach: truewas passed but no release exists for the tag:attach_and_replace-- bothattach: trueandreplace: truewere passed; they are mutually exclusive{:missing_input_dir, dir}-- input directory doesn't exist{:create_release_failed, status, body}-- GitHub API refused the release create (non-201, non-422){:find_release_failed, status, body}-- lookup for existing release during--replacefailed{:delete_release_failed, status, body}-- delete during--replacefailed{:upload_failed, name, status, body}-- asset upload returned non-2xx{:create_release_error, reason}-- transport failure (no HTTP response) while creating the release{:find_release_error, reason}-- transport failure while looking up the existing release during--replace{:delete_release_error, reason}-- transport failure while deleting the existing release during--replace{:upload_error, name, reason}-- asset upload transport failure"... :github :repo is unresolved ..."(string) -- the tinfoilgithub.repoconfig isn't set and couldn't be inferred from git
@type mode() :: :create | :attach | :replace
How publish/2 obtains the release it uploads to.
:create-- create a new release for the tag (default):attach-- upload to a release something else already created:replace-- delete the existing release and create a fresh one
Functions
@spec publish(Tinfoil.Config.t(), opts()) :: {:ok, result()} | {:ok, preview()} | {:error, error() | term()}
Publish release archives from input_dir (default "artifacts") to
a new GitHub Release on the repo configured in the tinfoil config.
The set of assets uploaded is every *.tar.gz or *.zip in
input_dir, plus the combined checksums-sha256.txt produced from
their .sha256 sidecars.
Authentication
Requires a GITHUB_TOKEN environment variable (or a GH_TOKEN
fallback) with contents: write permission on the target repo.
Tag
The tag to release against is taken from opts[:tag] if given,
otherwise from the GITHUB_REF_NAME environment variable (which
CI sets automatically on tag pushes). Versions matching -rc,
-beta, or -alpha are auto-marked as prerelease.
Existing releases
By default, if a release for tag already exists, publish/2
returns {:error, :release_already_exists_no_replace} without
touching the existing release or its assets — failing fast is
safer than silently clobbering something a user already shipped.
Two escape hatches, which are mutually exclusive:
attach: true (or --attach) uploads to the release that already
exists and changes nothing about it — body, prerelease flag, and
draft state are left exactly as the other tool wrote them. This is
the mode for projects where something else owns release creation:
release-please tags the version, creates the release, and writes the
curated changelog as the body, and tinfoil only adds the binaries.
It errors with :release_not_found_for_attach when no release
exists, since attaching to nothing is always a mistake.
replace: true (or --replace) deletes and recreates the existing
release, which discards whatever body it had. The git tag itself is
never touched; only the release object and its attached assets are
removed before the new release is created. Use this for development
and force-retag iteration loops, not for published versions.
Attach mode only makes sense when the workflow runs after the
release exists. Set trigger: :release_published in the tinfoil
config so the generated workflow fires on the release event rather
than racing it on the tag push.