221 lines
7.0 KiB
Bash
Executable File
221 lines
7.0 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
ROOT="$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
|
|
FORMULA_SOURCE="${DWS_FORMULA_SOURCE:-$ROOT/dist/homebrew/dingtalk-workspace-cli.rb}"
|
|
TAP_REPO_URL="${DWS_TAP_REPO_URL:-}"
|
|
TAP_BRANCH="${DWS_TAP_BRANCH:-main}"
|
|
TAP_FORMULA_PATH="${DWS_TAP_FORMULA_PATH:-Formula/dingtalk-workspace-cli.rb}"
|
|
TAP_GITHUB_TOKEN="${DWS_TAP_GITHUB_TOKEN:-}"
|
|
TAP_SSH_KEY="${DWS_TAP_SSH_KEY:-}"
|
|
PR_REPOSITORY="${DWS_TAP_PR_REPOSITORY:-}"
|
|
PR_BRANCH="${DWS_TAP_PR_BRANCH:-}"
|
|
PR_TITLE="${DWS_TAP_PR_TITLE:-chore: update Homebrew formula}"
|
|
COMMIT_MESSAGE="${DWS_TAP_COMMIT_MESSAGE:-chore: update dingtalk-workspace-cli formula}"
|
|
GIT_NAME="${DWS_GIT_NAME:-DWS Release Bot}"
|
|
GIT_EMAIL="${DWS_GIT_EMAIL:-dws-release-bot@example.com}"
|
|
PUBLISH_OUTPUT="${DWS_PUBLISH_OUTPUT:-${GITHUB_OUTPUT:-}}"
|
|
|
|
say() {
|
|
printf '%s\n' "$*"
|
|
}
|
|
|
|
err() {
|
|
printf 'error: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
need_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || err "missing required command: $1"
|
|
}
|
|
|
|
need_file() {
|
|
[ -f "$1" ] || err "required file not found: $1"
|
|
}
|
|
|
|
need_env() {
|
|
name="$1"
|
|
value="$2"
|
|
[ -n "$value" ] || err "missing required environment variable: $name"
|
|
}
|
|
|
|
record_direct_result() {
|
|
changed="$1"
|
|
commit="$2"
|
|
[ -z "$PUBLISH_OUTPUT" ] || {
|
|
printf 'formula_changed=%s\npublished_commit=%s\n' "$changed" "$commit" \
|
|
>> "$PUBLISH_OUTPUT"
|
|
}
|
|
}
|
|
|
|
checkout_tap_branch() {
|
|
repo_url="$1"
|
|
branch="$2"
|
|
target_dir="$3"
|
|
|
|
if git clone --branch "$branch" "$repo_url" "$target_dir" >/dev/null 2>&1; then
|
|
return
|
|
fi
|
|
|
|
git clone "$repo_url" "$target_dir" >/dev/null 2>&1
|
|
(
|
|
cd "$target_dir"
|
|
if git show-ref --verify --quiet "refs/remotes/origin/$branch"; then
|
|
git checkout -B "$branch" "origin/$branch" >/dev/null 2>&1
|
|
exit 0
|
|
fi
|
|
if git show-ref --verify --quiet "refs/heads/$branch"; then
|
|
git checkout "$branch" >/dev/null 2>&1
|
|
exit 0
|
|
fi
|
|
git checkout --orphan "$branch" >/dev/null 2>&1
|
|
)
|
|
}
|
|
|
|
need_cmd git
|
|
need_cmd ruby
|
|
need_env "DWS_TAP_REPO_URL" "$TAP_REPO_URL"
|
|
need_file "$FORMULA_SOURCE"
|
|
|
|
if grep -Eq '__[A-Z0-9_]+__' "$FORMULA_SOURCE"; then
|
|
err "formula contains unresolved template placeholders: $FORMULA_SOURCE"
|
|
fi
|
|
ruby -c "$FORMULA_SOURCE" >/dev/null || err "formula has invalid Ruby syntax: $FORMULA_SOURCE"
|
|
|
|
TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-homebrew-publish-XXXXXX")"
|
|
cleanup() {
|
|
rm -rf "$TMP_ROOT"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
if [ -n "$TAP_SSH_KEY" ]; then
|
|
SSH_KEY_PATH="$TMP_ROOT/tap-deploy-key"
|
|
printf '%s\n' "$TAP_SSH_KEY" > "$SSH_KEY_PATH"
|
|
chmod 600 "$SSH_KEY_PATH"
|
|
export GIT_SSH_COMMAND="ssh -i $SSH_KEY_PATH -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
|
|
export GIT_TERMINAL_PROMPT=0
|
|
fi
|
|
if [ -n "$TAP_GITHUB_TOKEN" ]; then
|
|
ASKPASS="$TMP_ROOT/git-askpass.sh"
|
|
# shellcheck disable=SC2016 # Preserve variables for the generated helper.
|
|
printf '%s\n' \
|
|
'#!/bin/sh' \
|
|
'case "$1" in' \
|
|
' *Username*) printf "%s\n" "x-access-token" ;;' \
|
|
' *) printf "%s\n" "$DWS_TAP_GITHUB_TOKEN" ;;' \
|
|
'esac' > "$ASKPASS"
|
|
chmod 700 "$ASKPASS"
|
|
export DWS_TAP_GITHUB_TOKEN GIT_ASKPASS="$ASKPASS"
|
|
export GIT_TERMINAL_PROMPT=0
|
|
fi
|
|
|
|
if [ -n "$PR_REPOSITORY" ] || [ -n "$PR_BRANCH" ]; then
|
|
need_env "DWS_TAP_PR_REPOSITORY" "$PR_REPOSITORY"
|
|
need_env "DWS_TAP_PR_BRANCH" "$PR_BRANCH"
|
|
need_env "DWS_TAP_GITHUB_TOKEN" "$TAP_GITHUB_TOKEN"
|
|
need_cmd gh
|
|
fi
|
|
|
|
if [ -z "$PR_REPOSITORY" ]; then
|
|
case "$TAP_FORMULA_PATH" in
|
|
Formula/dingtalk-workspace-cli.rb|Formula/dingtalk-workspace-cli-beta.rb) ;;
|
|
*) err "direct Homebrew publication is restricted to the stable or beta DWS Formula" ;;
|
|
esac
|
|
attempt=1
|
|
max_attempts=3
|
|
while [ "$attempt" -le "$max_attempts" ]; do
|
|
TAP_DIR="$TMP_ROOT/tap-direct-$attempt"
|
|
checkout_tap_branch "$TAP_REPO_URL" "$TAP_BRANCH" "$TAP_DIR"
|
|
|
|
DEST_PATH="$TAP_DIR/$TAP_FORMULA_PATH"
|
|
mkdir -p "$(dirname "$DEST_PATH")"
|
|
cp "$FORMULA_SOURCE" "$DEST_PATH"
|
|
|
|
if (
|
|
cd "$TAP_DIR" || exit $?
|
|
formula_status="$(git status --porcelain --untracked-files=all)" || exit $?
|
|
if [ -z "$formula_status" ]; then
|
|
formula_commit="$(git log -1 --format=%H -- "$TAP_FORMULA_PATH")" || exit $?
|
|
[ -n "$formula_commit" ] ||
|
|
err "could not resolve the existing commit for $TAP_FORMULA_PATH"
|
|
record_direct_result false "$formula_commit" || exit $?
|
|
exit 20
|
|
fi
|
|
case "$formula_status" in
|
|
" M $TAP_FORMULA_PATH"|"?? $TAP_FORMULA_PATH") ;;
|
|
*) err "direct Homebrew publication may change only $TAP_FORMULA_PATH" ;;
|
|
esac
|
|
git config user.name "$GIT_NAME" || exit $?
|
|
git config user.email "$GIT_EMAIL" || exit $?
|
|
git add "$TAP_FORMULA_PATH" || exit $?
|
|
staged_paths="$(git diff --cached --name-only)" || exit $?
|
|
[ "$staged_paths" = "$TAP_FORMULA_PATH" ] ||
|
|
err "staged Homebrew publication contains files other than $TAP_FORMULA_PATH"
|
|
git commit -m "$COMMIT_MESSAGE" >/dev/null || exit $?
|
|
committed_paths="$(git diff-tree --no-commit-id --name-only -r HEAD)" || exit $?
|
|
[ "$committed_paths" = "$TAP_FORMULA_PATH" ] ||
|
|
err "Homebrew publication commit contains files other than $TAP_FORMULA_PATH"
|
|
# Never force the tap's default branch. A concurrent main update gets a
|
|
# fresh clone and a newly based Formula-only commit on the next attempt.
|
|
git push origin "HEAD:$TAP_BRANCH" >/dev/null || exit $?
|
|
published_commit="$(git rev-parse HEAD)" || exit $?
|
|
record_direct_result true "$published_commit" || exit $?
|
|
); then
|
|
say "Published Homebrew formula to $TAP_REPO_URL ($TAP_BRANCH)"
|
|
exit 0
|
|
else
|
|
status=$?
|
|
fi
|
|
|
|
if [ "$status" -eq 20 ]; then
|
|
say "No formula changes to publish."
|
|
exit 0
|
|
fi
|
|
if [ "$attempt" -eq "$max_attempts" ]; then
|
|
err "could not publish Homebrew formula after $max_attempts non-forced attempts"
|
|
fi
|
|
say "Homebrew target branch moved or rejected the update; retrying from a fresh clone."
|
|
attempt=$((attempt + 1))
|
|
done
|
|
fi
|
|
|
|
TAP_DIR="$TMP_ROOT/tap-pr"
|
|
checkout_tap_branch "$TAP_REPO_URL" "$TAP_BRANCH" "$TAP_DIR"
|
|
|
|
DEST_PATH="$TAP_DIR/$TAP_FORMULA_PATH"
|
|
mkdir -p "$(dirname "$DEST_PATH")"
|
|
cp "$FORMULA_SOURCE" "$DEST_PATH"
|
|
|
|
(
|
|
cd "$TAP_DIR"
|
|
if [ -z "$(git status --short -- "$TAP_FORMULA_PATH")" ]; then
|
|
say "No formula changes to publish."
|
|
exit 0
|
|
fi
|
|
|
|
git config user.name "$GIT_NAME"
|
|
git config user.email "$GIT_EMAIL"
|
|
git add "$TAP_FORMULA_PATH"
|
|
git commit -m "$COMMIT_MESSAGE" >/dev/null
|
|
git push --force-with-lease origin "HEAD:$PR_BRANCH" >/dev/null
|
|
pr_url="$(
|
|
GH_TOKEN="$TAP_GITHUB_TOKEN" gh pr list \
|
|
--repo "$PR_REPOSITORY" \
|
|
--head "$PR_BRANCH" \
|
|
--state open \
|
|
--json url \
|
|
--jq '.[0].url'
|
|
)"
|
|
if [ -z "$pr_url" ]; then
|
|
pr_url="$(
|
|
GH_TOKEN="$TAP_GITHUB_TOKEN" gh pr create \
|
|
--repo "$PR_REPOSITORY" \
|
|
--base "$TAP_BRANCH" \
|
|
--head "$PR_BRANCH" \
|
|
--title "$PR_TITLE" \
|
|
--body "Automated Homebrew Formula update. Merge after required checks pass."
|
|
)"
|
|
fi
|
|
say "Opened Homebrew formula PR: $pr_url"
|
|
)
|