fix: refactor: extract disinto_release() from bin/disinto into lib/release.sh (#304)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-06 19:51:59 +00:00
parent 84d74ce541
commit e22863eb60
2 changed files with 176 additions and 144 deletions

View file

@ -31,6 +31,7 @@ source "${FACTORY_ROOT}/lib/forge-setup.sh"
source "${FACTORY_ROOT}/lib/generators.sh"
source "${FACTORY_ROOT}/lib/forge-push.sh"
source "${FACTORY_ROOT}/lib/ci-setup.sh"
source "${FACTORY_ROOT}/lib/release.sh"
# ── Helpers ──────────────────────────────────────────────────────────────────
@ -1295,150 +1296,7 @@ disinto_shell() {
# disinto_hire_an_agent() is sourced from lib/hire-agent.sh
# ── release command ───────────────────────────────────────────────────────────
#
# Creates a vault PR for the release. This is a convenience wrapper that
# creates the vault item TOML and submits it as a PR to the ops repo.
#
# Usage: disinto release <version>
# Example: disinto release v1.2.0
disinto_release() {
local version="${1:-}"
local formula_path="${FACTORY_ROOT}/formulas/release.toml"
if [ -z "$version" ]; then
echo "Error: version required" >&2
echo "Usage: disinto release <version>" >&2
echo "Example: disinto release v1.2.0" >&2
exit 1
fi
# Validate version format (must start with 'v' followed by semver)
if ! echo "$version" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Error: version must be in format v1.2.3 (semver with 'v' prefix)" >&2
exit 1
fi
# Load project config to get FORGE_OPS_REPO
if [ -z "${PROJECT_NAME:-}" ]; then
# PROJECT_NAME is unset - detect project TOML from projects/ directory
local found_toml
found_toml=$(find "${FACTORY_ROOT}/projects" -maxdepth 1 -name "*.toml" ! -name "*.example" 2>/dev/null | head -1)
if [ -n "$found_toml" ]; then
source "${FACTORY_ROOT}/lib/load-project.sh" "$found_toml"
fi
else
local project_toml="${FACTORY_ROOT}/projects/${PROJECT_NAME}.toml"
if [ -f "$project_toml" ]; then
source "${FACTORY_ROOT}/lib/load-project.sh" "$project_toml"
fi
fi
# Check formula exists
if [ ! -f "$formula_path" ]; then
echo "Error: release formula not found at ${formula_path}" >&2
exit 1
fi
# Get the ops repo root
local ops_root="${FACTORY_ROOT}/../disinto-ops"
if [ ! -d "${ops_root}/.git" ]; then
echo "Error: ops repo not found at ${ops_root}" >&2
echo " Run 'disinto init' to set up the ops repo first" >&2
exit 1
fi
# Generate a unique ID for the vault item
local id="release-${version//./}"
local vault_toml="${ops_root}/vault/actions/${id}.toml"
# Create vault TOML with the specific version
cat > "$vault_toml" <<EOF
# vault/actions/${id}.toml
# Release vault item for ${version}
# Auto-generated by disinto release
id = "${id}"
formula = "release"
context = "Release ${version}"
secrets = []
EOF
echo "Created vault item: ${vault_toml}"
# Create a PR to submit the vault item to the ops repo
local branch_name="release/${version//./}"
local pr_title="release: ${version}"
local pr_body="Release ${version}
This PR creates a vault item for the release of version ${version}.
## Changes
- Added vault item: ${id}.toml
## Next Steps
1. Review this PR
2. Approve and merge
3. The vault runner will execute the release formula
"
# Create branch from clean main
cd "$ops_root"
git checkout main
git pull origin main
git checkout -B "$branch_name" main
# Add and commit only the vault TOML file
git add "vault/actions/${id}.toml"
git commit -m "$pr_title" -m "$pr_body" 2>/dev/null || true
# Push branch
git push -u origin "$branch_name" 2>/dev/null || {
echo "Error: failed to push branch" >&2
exit 1
}
# Create PR
local pr_response
pr_response=$(curl -sf -X POST \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/repos/${FORGE_OPS_REPO}/pulls" \
-d "{\"title\":\"${pr_title}\",\"head\":\"${branch_name}\",\"base\":\"main\",\"body\":\"$(echo "$pr_body" | sed ':a;N;$!ba;s/\n/\\n/g')\"}" 2>/dev/null) || {
echo "Error: failed to create PR" >&2
echo "Response: ${pr_response}" >&2
exit 1
}
local pr_number
pr_number=$(echo "$pr_response" | jq -r '.number')
local pr_url="${FORGE_URL}/${FORGE_OPS_REPO}/pulls/${pr_number}"
# Enable auto-merge on the PR — Forgejo will auto-merge after approval
_vault_log "Enabling auto-merge for PR #${pr_number}"
curl -sf -X POST \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/repos/${FORGE_OPS_REPO}/pulls/${pr_number}/merge" \
-d '{"Do":"merge","merge_when_checks_succeed":true}' >/dev/null 2>&1 || {
echo "Warning: failed to enable auto-merge (may already be enabled or not supported)" >&2
}
echo ""
echo "Release PR created: ${pr_url}"
echo ""
echo "Next steps:"
echo " 1. Review the PR"
echo " 2. Approve the PR (auto-merge will trigger after approval)"
echo " 3. The vault runner will execute the release formula"
echo ""
echo "After merge, the release will:"
echo " 1. Tag Forgejo main with ${version}"
echo " 2. Push tag to mirrors (Codeberg, GitHub)"
echo " 3. Build and tag the agents Docker image"
echo " 4. Restart agent containers"
}
# disinto_release() is sourced from lib/release.sh
# ── ci-logs command ──────────────────────────────────────────────────────────
# Reads CI logs from the Woodpecker SQLite database.

174
lib/release.sh Normal file
View file

@ -0,0 +1,174 @@
#!/usr/bin/env bash
# =============================================================================
# release.sh — disinto_release() function
#
# Handles vault TOML creation, branch setup on ops repo, PR creation,
# and auto-merge request for a versioned release.
#
# Globals expected:
# FORGE_URL - Forge instance URL (e.g. http://localhost:3000)
# FORGE_TOKEN - API token for Forge operations
# FORGE_OPS_REPO - Ops repo slug (e.g. disinto-admin/myproject-ops)
# FACTORY_ROOT - Root of the disinto factory
# PRIMARY_BRANCH - Primary branch name (e.g. main)
#
# Usage:
# source "${FACTORY_ROOT}/lib/release.sh"
# disinto_release <version>
# =============================================================================
set -euo pipefail
# Source vault.sh for _vault_log helper
source "${FACTORY_ROOT}/lib/vault.sh"
# Assert required globals are set before using this module.
_assert_release_globals() {
local missing=()
[ -z "${FORGE_URL:-}" ] && missing+=("FORGE_URL")
[ -z "${FORGE_TOKEN:-}" ] && missing+=("FORGE_TOKEN")
[ -z "${FORGE_OPS_REPO:-}" ] && missing+=("FORGE_OPS_REPO")
[ -z "${FACTORY_ROOT:-}" ] && missing+=("FACTORY_ROOT")
[ -z "${PRIMARY_BRANCH:-}" ] && missing+=("PRIMARY_BRANCH")
if [ "${#missing[@]}" -gt 0 ]; then
echo "Error: release.sh requires these globals to be set: ${missing[*]}" >&2
exit 1
fi
}
disinto_release() {
local version="${1:-}"
local formula_path="${FACTORY_ROOT}/formulas/release.toml"
if [ -z "$version" ]; then
echo "Error: version required" >&2
echo "Usage: disinto release <version>" >&2
echo "Example: disinto release v1.2.0" >&2
exit 1
fi
# Validate version format (must start with 'v' followed by semver)
if ! echo "$version" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Error: version must be in format v1.2.3 (semver with 'v' prefix)" >&2
exit 1
fi
# Load project config to get FORGE_OPS_REPO
if [ -z "${PROJECT_NAME:-}" ]; then
# PROJECT_NAME is unset - detect project TOML from projects/ directory
local found_toml
found_toml=$(find "${FACTORY_ROOT}/projects" -maxdepth 1 -name "*.toml" ! -name "*.example" 2>/dev/null | head -1)
if [ -n "$found_toml" ]; then
source "${FACTORY_ROOT}/lib/load-project.sh" "$found_toml"
fi
else
local project_toml="${FACTORY_ROOT}/projects/${PROJECT_NAME}.toml"
if [ -f "$project_toml" ]; then
source "${FACTORY_ROOT}/lib/load-project.sh" "$project_toml"
fi
fi
# Check formula exists
if [ ! -f "$formula_path" ]; then
echo "Error: release formula not found at ${formula_path}" >&2
exit 1
fi
# Get the ops repo root
local ops_root="${FACTORY_ROOT}/../disinto-ops"
if [ ! -d "${ops_root}/.git" ]; then
echo "Error: ops repo not found at ${ops_root}" >&2
echo " Run 'disinto init' to set up the ops repo first" >&2
exit 1
fi
# Generate a unique ID for the vault item
local id="release-${version//./}"
local vault_toml="${ops_root}/vault/actions/${id}.toml"
# Create vault TOML with the specific version
cat > "$vault_toml" <<EOF
# vault/actions/${id}.toml
# Release vault item for ${version}
# Auto-generated by disinto release
id = "${id}"
formula = "release"
context = "Release ${version}"
secrets = []
EOF
echo "Created vault item: ${vault_toml}"
# Create a PR to submit the vault item to the ops repo
local branch_name="release/${version//./}"
local pr_title="release: ${version}"
local pr_body="Release ${version}
This PR creates a vault item for the release of version ${version}.
## Changes
- Added vault item: ${id}.toml
## Next Steps
1. Review this PR
2. Approve and merge
3. The vault runner will execute the release formula
"
# Create branch from clean main
cd "$ops_root"
git checkout main
git pull origin main
git checkout -B "$branch_name" main
# Add and commit only the vault TOML file
git add "vault/actions/${id}.toml"
git commit -m "$pr_title" -m "$pr_body" 2>/dev/null || true
# Push branch
git push -u origin "$branch_name" 2>/dev/null || {
echo "Error: failed to push branch" >&2
exit 1
}
# Create PR
local pr_response
pr_response=$(curl -sf -X POST \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/repos/${FORGE_OPS_REPO}/pulls" \
-d "{\"title\":\"${pr_title}\",\"head\":\"${branch_name}\",\"base\":\"main\",\"body\":\"$(echo "$pr_body" | sed ':a;N;$!ba;s/\n/\\n/g')\"}" 2>/dev/null) || {
echo "Error: failed to create PR" >&2
echo "Response: ${pr_response}" >&2
exit 1
}
local pr_number
pr_number=$(echo "$pr_response" | jq -r '.number')
local pr_url="${FORGE_URL}/${FORGE_OPS_REPO}/pulls/${pr_number}"
# Enable auto-merge on the PR — Forgejo will auto-merge after approval
_vault_log "Enabling auto-merge for PR #${pr_number}"
curl -sf -X POST \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/repos/${FORGE_OPS_REPO}/pulls/${pr_number}/merge" \
-d '{"Do":"merge","merge_when_checks_succeed":true}' >/dev/null 2>&1 || {
echo "Warning: failed to enable auto-merge (may already be enabled or not supported)" >&2
}
echo ""
echo "Release PR created: ${pr_url}"
echo ""
echo "Next steps:"
echo " 1. Review the PR"
echo " 2. Approve the PR (auto-merge will trigger after approval)"
echo " 3. The vault runner will execute the release formula"
echo ""
echo "After merge, the release will:"
echo " 1. Tag Forgejo main with ${version}"
echo " 2. Push tag to mirrors (Codeberg, GitHub)"
echo " 3. Build and tag the agents Docker image"
echo " 4. Restart agent containers"
}