Skip to main content

Release management CLI for Haviland Software projects

This project has been archived.

The maintainers of this project have marked this project as archived. No new releases are expected.

Project description

pixelfuel-blastoff

A small CLI to batch GitHub releases and hotfixes across multiple repositories grouped by a GitHub topic.

blastoff computes the next semantic version for a group of repos, tags every repository that carries a given GitHub topic, generates a per-repo changelog of merged pull requests, and (optionally) a Claude-authored release summary. Hotfixes bump the patch version, with two supported modes.

  • Install / distribution name: pixelfuel-blastoff (on PyPI)
  • Import / command name: blastoff

What it does

  • Batch releases by topic — point it at a GitHub org and a topic; it finds every repo carrying that topic and creates the same release tag on each (skipping archived repos and repos that already have the tag).
  • Automatic version computation — the next version is resolved from a local config by alias, so you don't hand-type tags. A release bumps the minor version; a hotfix bumps the patch.
  • Changelog generation — collects merged PRs across all matched repos since the last release/hotfix and writes a structured per-repo changelog to CHANGELOG.md.
  • Optional Claude release summary — when a Claude API key is available, an executive-facing narrative of the release is generated; otherwise blastoff falls back to PR titles.
  • Dry run by default — every release/hotfix is a preview unless you add --release.
  • Pluggable version backend — release state lives in a local file by default, but you can implement your own VersionStore to keep it anywhere (see Extending).

Install

With pip:

pip install pixelfuel-blastoff

With uv:

uv pip install pixelfuel-blastoff
# or add it to a project
uv add pixelfuel-blastoff

The distribution is named pixelfuel-blastoff, but the CLI and the import package are both blastoff:

blastoff --help            # the CLI is on PATH after install
python -c "import blastoff" # import by package name

Prerequisites

blastoff talks to the GitHub API and, optionally, to Claude. Configure it with environment variables (a .env file in the working directory is loaded automatically):

Variable Required Purpose
GH_TOKEN yes GitHub token used to list repos and create releases. Scopes: repo, read:org.
GH_ORG yes* GitHub organization to pull repositories from.
TOPIC yes* GitHub topic used to select which repos to release.
CLAUDE_API_KEY no Enables Claude-generated PR summaries (dry run) and the release narrative. Without it, blastoff falls back to PR titles / a plain summary.

* GH_ORG and TOPIC can also be supplied per-invocation via CLI flags, or resolved from config by alias (see below). When you resolve a release by alias (-c <alias>), the org and topic come from the config entry and you don't need the env vars.

Usage

blastoff has four subcommands: release, hotfix, summarize, and version.

Releases

The version and topic are resolved from config by alias. Do not pass -t/--tag for a topic/alias release — the tag is computed for you from the config's next_version. Passing -t is a one-off override that bypasses config resolution and never mutates stored version state.

# Dry run — shows what would be released, writes nothing (safe to run anytime)
blastoff release -c <alias>

# Actually create the releases
blastoff release -c <alias> --release

# Release only a single repository
blastoff release -c <alias> --repo <repo-name> --release

Useful flags:

  • --release / -r — perform the release. Omitting it is always a dry run.
  • --repo <name> — restrict to a single repository.
  • --summary "text" — use this text verbatim as the release summary instead of auto-generating one.
  • --changelog-output <path> — where to write the changelog (default: CHANGELOG.md).
  • -o/--org, -k/--token, -c/--topic — override GH_ORG, GH_TOKEN, TOPIC.

On a real release, blastoff validates the token, creates the tag on each matching repo, writes the changelog, bumps the stored next_version (minor bump), and records the released version via the configured backend.

Hotfixes

Hotfixes bump the patch version from the last released version. Two modes are supported per alias (set with the hotfix_mode field in config):

Mode Behavior Example
increment (default) Bumps the patch digit v1.3.0v1.3.1
hotfix Appends a -hotfix.N suffix v1.3.0v1.3.0-hotfix.1

blastoff scans existing GitHub releases in the topic to pick the next free hotfix number, so re-runs are safe.

# Dry run
blastoff hotfix -c <alias>

# Create the hotfix releases
blastoff hotfix -c <alias> --release

# Hotfix a single repo
blastoff hotfix -c <alias> --repo <repo-name> --release

# Hotfix a single repo at a specific commit
blastoff hotfix -c <alias> --repo <repo-name> --commit <sha> --release

A full release must have run first (the hotfix base is the config's last_released_version).

Summaries

summarize produces a Markdown release document (written to releases/ by default):

blastoff summarize                       # all configured aliases
blastoff summarize --alias <alias>       # a single alias
blastoff summarize --output-dir <path>   # custom output directory

Version config

version inspects the resolved config:

blastoff version print   # show the version state for all configured aliases
blastoff version check   # validate the config and report any errors

Configuration

Version and release state is read from a local file. blastoff looks for two sources, in order, walking up from the current directory:

  1. .innoday/project.yml — a project config file blastoff reads if present. Add a release_configs list to it (this is optional; it exists so blastoff can share a project's config file rather than requiring a separate one).
  2. org-versions.json — the standalone fallback, holding local per-machine release state.

The resolution logic lives in blastoff/config_loader.py, and the default file backend is blastoff/stores/file_store.py.

org-versions.json

Create it in your project root (or any parent directory), with one entry per alias:

{
  "organizations": [
    {
      "alias": "my-app",
      "organization": "my-github-org",
      "label": "my-release-topic",
      "next_version": "v1.2.0",
      "prerelease": null,
      "last_released": null,
      "last_released_version": null,
      "last_hotfix": null,
      "hotfix_mode": "increment"
    }
  ]
}

Field reference:

Field Required Meaning
alias yes The name you pass to -c — how you refer to this release group.
organization yes GitHub organization the repos live in.
label yes GitHub topic that selects the repos to release.
next_version yes The version to release next (e.g. v1.2.0). Bumped automatically after a real release.
prerelease no Prerelease type: alpha, beta, or rc.
last_released no ISO-8601 timestamp of the last release (set automatically).
last_released_version no The last version released (set automatically; used as the hotfix base).
last_hotfix no The last hotfix tag created (set automatically).
hotfix_mode no increment (default) or hotfix — see the hotfix table above.

After a successful release or hotfix, whichever source the config was loaded from is updated automatically with the new version state.

.innoday/project.ymlrelease_configs

If you keep a .innoday/project.yml, blastoff will read a release_configs list from it (checked before org-versions.json), with the same fields per alias:

release_configs:
  - alias: my-app
    organization: my-github-org
    label: my-release-topic
    next_version: v1.2.0
    prerelease: null
    last_released: null
    last_released_version: null
    last_hotfix: null
    hotfix_mode: increment

Extending — custom version backends

Where release state lives is pluggable. blastoff computes versions and tags repos; where the version state is loaded from and where a completed release is recorded is abstracted behind the VersionStore interface (blastoff.stores.VersionStore). The shipped default is FileVersionStore (the file-backed backend above).

To store release state somewhere else — a database, an internal API, a service — implement the three abstract methods and inject your store into the release/hotfix commands:

from blastoff.stores import VersionStore
from blastoff.version_manager import OrgConfig


class MyVersionStore(VersionStore):
    def load_org_config(self, alias: str) -> OrgConfig:
        """Return the OrgConfig for `alias` (raise FileNotFoundError if none)."""
        ...

    def save_org_config(self, org: OrgConfig) -> None:
        """Persist an updated OrgConfig back to your backend."""
        ...

    def record_release(self, org, version, released_at=None, summary=None, changelog=None) -> None:
        """Record that `version` was released for `org` (called after tagging)."""
        ...

Both the Release and Hotfix commands expose a version_store attribute; set it before invoking to swap the backend. When left unset it defaults to FileVersionStore, giving you the standalone file-backed behavior described above. blastoff imports nothing from any consumer — the dependency arrow points one way: your code depends on blastoff and implements this interface.

Testing

uv run pytest

License

MIT — see LICENSE.

To publish a new version to PyPI, see PUBLISHING.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pixelfuel_blastoff-0.1.0.tar.gz (46.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pixelfuel_blastoff-0.1.0-py3-none-any.whl (39.6 kB view details)

Uploaded Python 3

File details

Details for the file pixelfuel_blastoff-0.1.0.tar.gz.

File metadata

  • Download URL: pixelfuel_blastoff-0.1.0.tar.gz
  • Upload date:
  • Size: 46.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pixelfuel_blastoff-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0a5a684a8e8aac4ad05f556daa2b4e7c410cde45b8677e224b5b9d289a20c065
MD5 d3e78bdf128a5b434a6b949a292001f3
BLAKE2b-256 e7f84477d4bf177e66567deac51873b8340a1a14a2c99fc7dcb5681384be4ecb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pixelfuel_blastoff-0.1.0.tar.gz:

Publisher: publish.yml on havilandsoftware/pixelfuel-blastoff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pixelfuel_blastoff-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pixelfuel_blastoff-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f67861d00d4c96264a9a5c90e3b232190c97f455537b8831ef566cfab9729101
MD5 c100dfe644ed878ca6311c7a4391a80d
BLAKE2b-256 dbe6bc452222a233e32d82dce76a9fb00ab410c31d73670fa35a5ab132b482e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pixelfuel_blastoff-0.1.0-py3-none-any.whl:

Publisher: publish.yml on havilandsoftware/pixelfuel-blastoff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page