This repository has no description
README.md

Tangled release automation #

Reusable release-PR and publishing commands for Tangled Spindles. They provide the release loop normally handled by hosted CI actions while leaving configuration in each repository's workflow.

Warning

This project is experimental. Tangled and Spindles are evolving quickly, and the automation performs privileged release operations including force-pushing a release branch, publishing artifacts, and creating tags. Validate it on a non-critical release before relying on it in production.

Two commands are packaged as a Nix flake:

  • tangled-release-pr maintains a long-running chore/release branch and its pull-request rounds.
  • tangled-publish publishes a merged release, optionally deploys it, and pushes its tags.

Spindle installs them directly from this repository with its native flake dependency support:

engine: microvm
image: nixos

dependencies:
  - git+https://tangled.org/natemoo.re/automation#release-pr
  - git+https://tangled.org/natemoo.re/automation#git-cliff
  - git+https://tangled.org/natemoo.re/automation#tg

steps:
  - name: Maintain release PR
    command: tangled-release-pr

The consumer repository must have a Spindle selected in its Tangled Settings → Pipelines. Adding spindle to the repo record through the CLI does not currently activate the hosted runner.

Adapter tools are separate flake packages: #git-cliff, #pnpm, #railway, and #tg. Add only the ones used by that workflow (#tg is needed for automatic PR rounds). This keeps a Docker publish from realizing Node/pnpm, an npm publish from realizing Railway, and branch-only release automation from compiling tg.

Supported adapters #

Concern Adapter Behavior
Changelog changesets Installs the frozen pnpm workspace, then runs pnpm changeset version; pending changeset files determine whether a release is needed.
Changelog git-cliff Computes the next 0.x-aware SemVer, regenerates CHANGELOG.md, and writes VERSION.
Publish npm Installs the exact release workspace, runs pnpm changeset publish, repairs package tags, and pushes them.
Publish docker Builds and pushes immutable vX.Y.Z plus a configurable moving tag to Docker Hub.
Publish command Runs repository-owned idempotency and publish commands. Useful for Homebrew or binary releases.
Publish none Creates only the release tag. Useful when the tag itself distributes a Nix flake.
Deploy railway Redeploys a Railway service from its configured source after a new publish.
Deploy command Runs a repository-owned post-publish deploy command.

The deliberately supported combinations are:

  • changesets + npm
  • git-cliff + docker
  • git-cliff + command
  • git-cliff + none

Railway or a custom deploy command can follow any successful publish.

Configuration #

Configuration is inline environment in the consumer workflow. Secrets come from Tangled's repository settings.

Release PR #

Variable Required Default
AUTOMATION_REPOSITORY yes Tangled handle/repo, such as lgtm.shop/commerce
AUTOMATION_BOT_ACCOUNT for automatic PR rounds Tangled handle used with TG_APP_PASSWORD
AUTOMATION_BOT_NAME yes Git commit author name
AUTOMATION_BOT_EMAIL yes Git commit author email
AUTOMATION_ORIGIN_URL no git@tangled.org:<AUTOMATION_REPOSITORY>; override for a self-hosted knot
AUTOMATION_CHANGELOG no changesets
AUTOMATION_DEFAULT_BRANCH no main
AUTOMATION_RELEASE_BRANCH no chore/release
AUTOMATION_PR_TITLE no chore: release
AUTOMATION_VERSION_FILE no VERSION
AUTOMATION_CHANGELOG_FILE no CHANGELOG.md
KNOT_DEPLOY_KEY yes SSH private key registered to the bot account
TG_APP_PASSWORD for automatic PR rounds Bot account app password

Without TG_APP_PASSWORD, the release branch is still pushed but its PR must be maintained manually.

Publishing #

Variable Required Default
AUTOMATION_CHANGELOG no changesets
AUTOMATION_PUBLISH no npm
AUTOMATION_DEPLOY no none
NPM_TOKEN for npm none
AUTOMATION_DOCKER_IMAGE for docker none; docker.io/owner/image
AUTOMATION_DOCKER_CONTEXT no .
AUTOMATION_DOCKERFILE no Dockerfile
AUTOMATION_DOCKER_LATEST_TAG no latest
AUTOMATION_DOCKER_BUILD_ARGS no additional shell-split docker build arguments
REGISTRY_USER / REGISTRY_TOKEN for docker Docker Hub credentials
AUTOMATION_PUBLISH_CHECK_COMMAND for command returns zero when already published
AUTOMATION_PUBLISH_COMMAND for command repository-owned publish command
AUTOMATION_RAILWAY_SERVICE for Railway Railway service name or ID
RAILWAY_TOKEN for Railway project token scoped to the target environment
AUTOMATION_DEPLOY_COMMAND for command deploy repository-owned deploy command

KNOT_DEPLOY_KEY, AUTOMATION_REPOSITORY, AUTOMATION_BOT_NAME, and AUTOMATION_BOT_EMAIL are required by publishing because remote tags are its completion marker. Publishing and deployment happen before tag creation, allowing a failed run to repair and retry the incomplete release safely.

Both commands acquire short-lived Git-backed locks, so overlapping Spindle runs serialize instead of moving a release branch, npm dist-tag, Docker moving tag, or deployment backward. The default wait is 20 minutes and abandoned locks become replaceable after two hours; configure these with AUTOMATION_LOCK_WAIT_SECONDS and AUTOMATION_LOCK_STALE_SECONDS if a release routinely runs longer. Custom deployment commands must be idempotent because deployment has at-least-once semantics when artifact publication succeeds but tag pushing must be retried.

The command adapter exports AUTOMATION_VERSION when used with git-cliff. Its check must return 0 when that version is published and 1 when it is absent; any other status aborts the release. The publish command runs only for status 1.

Examples #

Release lifecycle #

  1. A push to the default branch runs tangled-release-pr.
  2. If releasable changes exist, it rebuilds chore/release, force-pushes it, and creates or updates the release PR round with tg.
  3. Merging the release PR runs tangled-publish.
  4. New artifacts are published, optional deployment runs, and release tags are pushed.
  5. Subsequent non-release pushes are idempotent: existing artifacts and tags are skipped.

Bot setup #

Generate a dedicated key without a passphrase, register its public half on the bot account, and store the private half as KNOT_DEPLOY_KEY:

ssh-keygen -t ed25519 -N "" -C "Tangled release bot" -f ./knot-deploy
tg ssh-key add ./knot-deploy.pub --account example.com --title "Spindle release bot"
rm ./knot-deploy ./knot-deploy.pub

Create an AT Protocol app password for the same account and store it as TG_APP_PASSWORD.

Development #

bash tests/test.sh
shellcheck -x scripts/*.sh scripts/lib/*.sh tests/test.sh
nix flake check
nix build .#release-pr

The repository's own Spindle runs these checks on Linux, which is also the environment used by consumer workflows. Its test workflow consumes #release-pr and #publish as separate external dependencies, verifying the same slim closures consumers receive.