Tinfoil.Homebrew (tinfoil v0.2.22)

Copy Markdown View Source

Render a Homebrew formula from release artifacts and push it to a tap repo. Earlier tinfoil versions shelled out to a generated scripts/update-homebrew.sh; this module replaces it so the whole release lifecycle stays in Elixir and is unit-testable.

Called from mix tinfoil.homebrew, which the generated workflow invokes on a separate CI job after the GitHub Release publish step succeeds. See Tinfoil.Publish for the release-assets counterpart.

Inputs at CI time

  • artifacts/ directory containing the release tarballs plus .sha256 sidecar files (downloaded from the build matrix)
  • GITHUB_REF_NAME env var holding the tag that triggered the release (e.g. v1.2.3)
  • Auth material depending on config.homebrew.auth:
    • :tokenHOMEBREW_TAP_TOKEN env with PAT
    • :deploy_key — SSH agent configured with a deploy key for the tap repo (typically via webfactory/ssh-agent)

What the module does

  1. Reads .sha256 sidecars for every target's archive and builds a map of target => sha256.
  2. Renders the user's checked-in .tinfoil/formula.rb.eex (falling back to the default template) with real version + SHAs.
  3. Clones the tap repo into a temp dir, writes the rendered formula at Formula/<formula_name>.rb, commits and pushes.

The git operations shell out to git — no extra deps. Authentication is left to the environment: HTTPS with a token-bearing URL, or SSH via an already-running ssh-agent.

Summary

Types

Known error atoms returned by publish/2. Catch-all is {:error, term()} for unexpected failures.

Functions

Render the formula and push it to the tap repo.

Types

error()

@type error() ::
  :homebrew_not_enabled
  | :missing_tag
  | :missing_homebrew_tap_token
  | :malformed_sha_sidecar
  | {:missing_sha_sidecar, atom(), Path.t()}
  | {:missing_formula_template, Path.t()}
  | {:formula_template_read_error, Path.t(), term()}
  | {:git_failed, [String.t()], non_neg_integer(), String.t()}

Known error atoms returned by publish/2. Catch-all is {:error, term()} for unexpected failures.

  • :homebrew_not_enabled -- config has homebrew.enabled: false
  • :missing_tag -- no tag given and GITHUB_REF_NAME is unset
  • :missing_homebrew_tap_token -- auth: :token but HOMEBREW_TAP_TOKEN env var is missing or empty
  • {:missing_sha_sidecar, target, path} -- a .sha256 sidecar file couldn't be read from input_dir
  • :malformed_sha_sidecar -- a .sha256 sidecar didn't contain a 64-hex-character digest
  • {:missing_formula_template, path} -- the EEx formula template couldn't be found (default .tinfoil/formula.rb.eex)
  • {:formula_template_read_error, path, reason} -- template read failed for a reason other than :enoent
  • {:git_failed, args, exit_status, output} -- a git subprocess call returned non-zero

opts()

@type opts() :: [
  input_dir: Path.t(),
  tag: String.t() | nil,
  formula_template: Path.t() | nil,
  tap_dir: Path.t() | nil,
  dry_run: boolean() | nil,
  git: module()
]

preview()

@type preview() :: %{
  dry_run: true,
  tap: String.t(),
  auth: :token | :deploy_key,
  clone_url: String.t(),
  formula_name: String.t(),
  formula: String.t(),
  commit_message: String.t()
}

result()

@type result() ::
  %{pushed: boolean(), formula_path: Path.t(), commit_sha: String.t() | nil}
  | %{pushed: false, skipped: :prerelease}

Functions

publish(config, opts \\ [])

@spec publish(Tinfoil.Config.t(), opts()) ::
  {:ok, result()} | {:ok, preview()} | {:error, error() | term()}

Render the formula and push it to the tap repo.

Options (all optional, sane defaults):

  • :input_dir — directory with archives + sha256 sidecars (default "artifacts")
  • :tag — release tag, usually taken from GITHUB_REF_NAME env
  • :formula_template — path to the EEx formula template (default ".tinfoil/formula.rb.eex")
  • :tap_dir — temp dir to clone into (default a new mktemp)
  • :git — module implementing the git callbacks (see Tinfoil.Homebrew.Git); injected for testing