Spaces:
Sleeping
Sleeping
| set -euo pipefail | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| LOCAL_TOKENS_FILE="${LOCAL_TOKENS_FILE:-$SCRIPT_DIR/local_tokens.sh}" | |
| if [[ -f "$LOCAL_TOKENS_FILE" ]]; then | |
| # shellcheck disable=SC1090 | |
| source "$LOCAL_TOKENS_FILE" | |
| fi | |
| REMOTE="${REMOTE:-github}" | |
| BASE_BRANCH="${BASE_BRANCH:-main}" | |
| WINDOWS_BRANCH="${WINDOWS_BRANCH:-windows}" | |
| WORKFLOW_FILE="${WORKFLOW_FILE:-build-windows-portable.yml}" | |
| ARTIFACT_NAME="${ARTIFACT_NAME:-MesaFrame-portable}" | |
| WAIT_FOR_RUN=1 | |
| DOWNLOAD_ARTIFACT=1 | |
| DOWNLOAD_DIR="" | |
| POLL_SECONDS=10 | |
| usage() { | |
| cat <<'EOF' | |
| Uso: | |
| ./scripts/release_windows.sh [opcoes] | |
| O script: | |
| 1. troca para a branch windows | |
| 2. faz merge da main | |
| 3. faz push da windows para o GitHub | |
| 4. dispara o workflow build-windows-portable | |
| 5. espera a execucao terminar | |
| 6. baixa o zip final para release/windows-gh/ | |
| Opcoes: | |
| --no-wait Dispara o workflow e encerra sem esperar | |
| --no-download Nao baixa o artefato ao final | |
| --download-dir DIR Diretorio onde salvar o zip final | |
| --remote NAME Remote GitHub (padrao: github) | |
| --base-branch NAME Branch base compartilhada (padrao: main) | |
| --windows-branch NAME Branch de release Windows (padrao: windows) | |
| --workflow FILE Workflow a disparar (padrao: build-windows-portable.yml) | |
| --help Mostra esta ajuda | |
| Autenticacao: | |
| - Se existir scripts/local_tokens.sh, ele sera carregado automaticamente. | |
| - Se o comando 'gh' estiver instalado e autenticado, ele sera usado. | |
| - Caso contrario, defina GH_TOKEN ou GITHUB_TOKEN para usar a API do GitHub. | |
| EOF | |
| } | |
| log() { | |
| printf '[release-windows] %s\n' "$*" | |
| } | |
| fail() { | |
| printf '[release-windows] erro: %s\n' "$*" >&2 | |
| exit 1 | |
| } | |
| require_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || fail "comando obrigatorio ausente: $1" | |
| } | |
| repo_root() { | |
| git rev-parse --show-toplevel | |
| } | |
| repo_slug_from_remote() { | |
| local remote_url | |
| remote_url="$(git remote get-url "$REMOTE")" | |
| case "$remote_url" in | |
| git@github.com:*) | |
| remote_url="${remote_url#git@github.com:}" | |
| remote_url="${remote_url%.git}" | |
| ;; | |
| https://github.com/*) | |
| remote_url="${remote_url#https://github.com/}" | |
| remote_url="${remote_url%.git}" | |
| ;; | |
| *) | |
| fail "nao consegui identificar owner/repo a partir do remote '$REMOTE': $remote_url" | |
| ;; | |
| esac | |
| printf '%s\n' "$remote_url" | |
| } | |
| ensure_clean_tracked_tree() { | |
| git diff --quiet || fail "ha alteracoes rastreadas nao commitadas; limpe a arvore antes de gerar a release" | |
| git diff --cached --quiet || fail "ha alteracoes staged; finalize o commit antes de gerar a release" | |
| } | |
| ensure_branch_exists() { | |
| git show-ref --verify --quiet "refs/heads/$1" || fail "branch local inexistente: $1" | |
| } | |
| auth_mode() { | |
| if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then | |
| printf 'gh\n' | |
| return 0 | |
| fi | |
| if [[ -n "${GH_TOKEN:-}" || -n "${GITHUB_TOKEN:-}" ]]; then | |
| printf 'token\n' | |
| return 0 | |
| fi | |
| fail "configure o comando 'gh' com 'gh auth login' ou exporte GH_TOKEN/GITHUB_TOKEN" | |
| } | |
| github_api_get() { | |
| local path="$1" | |
| local mode="$2" | |
| local slug="$3" | |
| if [[ "$mode" == "gh" ]]; then | |
| gh api "repos/$slug/$path" | |
| return | |
| fi | |
| local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}" | |
| curl --fail --silent --show-error --location \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer $token" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/$slug/$path" | |
| } | |
| github_api_post() { | |
| local path="$1" | |
| local mode="$2" | |
| local slug="$3" | |
| local body="$4" | |
| if [[ "$mode" == "gh" ]]; then | |
| gh api --method POST "repos/$slug/$path" --input - <<<"$body" >/dev/null | |
| return | |
| fi | |
| local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}" | |
| curl --fail --silent --show-error --location \ | |
| -X POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer $token" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| -d "$body" \ | |
| "https://api.github.com/repos/$slug/$path" >/dev/null | |
| } | |
| download_url() { | |
| local url="$1" | |
| local destination="$2" | |
| local mode="$3" | |
| if [[ "$mode" == "gh" ]]; then | |
| GH_FORCE_TTY=0 gh api "$url" >"$destination" | |
| return | |
| fi | |
| local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}" | |
| curl --fail --silent --show-error --location \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer $token" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/$url" >"$destination" | |
| } | |
| find_run_id() { | |
| local slug="$1" | |
| local mode="$2" | |
| local head_sha="$3" | |
| local attempts=0 | |
| local max_attempts=60 | |
| while (( attempts < max_attempts )); do | |
| local payload | |
| payload="$(github_api_get "actions/workflows/$WORKFLOW_FILE/runs?branch=$WINDOWS_BRANCH&event=workflow_dispatch&per_page=20" "$mode" "$slug")" | |
| local run_id | |
| run_id="$( | |
| python3 -c ' | |
| import json | |
| import sys | |
| target_sha = sys.argv[1] | |
| payload = json.load(sys.stdin) | |
| for run in payload.get("workflow_runs", []): | |
| if str(run.get("head_sha") or "").strip() == target_sha: | |
| print(run["id"]) | |
| break | |
| ' "$head_sha" <<<"$payload" | |
| )" | |
| if [[ -n "$run_id" ]]; then | |
| printf '%s\n' "$run_id" | |
| return 0 | |
| fi | |
| sleep "$POLL_SECONDS" | |
| attempts=$((attempts + 1)) | |
| done | |
| fail "nao encontrei a execucao do workflow para o commit $head_sha" | |
| } | |
| watch_run() { | |
| local slug="$1" | |
| local mode="$2" | |
| local run_id="$3" | |
| if [[ "$mode" == "gh" ]]; then | |
| gh run watch "$run_id" --repo "$slug" --interval "$POLL_SECONDS" | |
| return | |
| fi | |
| local last_status="" | |
| while true; do | |
| local payload | |
| payload="$(github_api_get "actions/runs/$run_id" "$mode" "$slug")" | |
| local parsed | |
| parsed="$( | |
| python3 -c ' | |
| import json | |
| import sys | |
| payload = json.load(sys.stdin) | |
| print(payload.get("status") or "") | |
| print(payload.get("conclusion") or "") | |
| print(payload.get("html_url") or "") | |
| ' <<<"$payload" | |
| )" | |
| local status conclusion html_url | |
| status="$(printf '%s\n' "$parsed" | sed -n '1p')" | |
| conclusion="$(printf '%s\n' "$parsed" | sed -n '2p')" | |
| html_url="$(printf '%s\n' "$parsed" | sed -n '3p')" | |
| if [[ "$status" != "$last_status" ]]; then | |
| log "workflow status: ${status:-desconhecido}" | |
| [[ -n "$html_url" ]] && log "acompanhe em: $html_url" | |
| last_status="$status" | |
| fi | |
| if [[ "$status" == "completed" ]]; then | |
| [[ "$conclusion" == "success" ]] || fail "workflow concluiu com status '$conclusion'" | |
| return 0 | |
| fi | |
| sleep "$POLL_SECONDS" | |
| done | |
| } | |
| download_final_zip() { | |
| local slug="$1" | |
| local mode="$2" | |
| local run_id="$3" | |
| local output_dir="$4" | |
| mkdir -p "$output_dir" | |
| local payload | |
| payload="$(github_api_get "actions/runs/$run_id/artifacts?per_page=100" "$mode" "$slug")" | |
| local download_path | |
| download_path="$( | |
| python3 -c ' | |
| import json | |
| import sys | |
| artifact_name = sys.argv[1] | |
| payload = json.load(sys.stdin) | |
| for artifact in payload.get("artifacts", []): | |
| if artifact.get("name") == artifact_name: | |
| url = artifact.get("archive_download_url") or "" | |
| if url.startswith("https://api.github.com/"): | |
| url = url.removeprefix("https://api.github.com/") | |
| print(url) | |
| break | |
| ' "$ARTIFACT_NAME" <<<"$payload" | |
| )" | |
| [[ -n "$download_path" ]] || fail "nao encontrei o artefato '$ARTIFACT_NAME' no run $run_id" | |
| local temp_dir | |
| temp_dir="$(mktemp -d)" | |
| local outer_zip="$temp_dir/github-artifact.zip" | |
| local extracted_dir="$temp_dir/extracted" | |
| mkdir -p "$extracted_dir" | |
| download_url "$download_path" "$outer_zip" "$mode" | |
| python3 - "$outer_zip" "$extracted_dir" <<'PY' | |
| import sys | |
| from zipfile import ZipFile | |
| with ZipFile(sys.argv[1]) as archive: | |
| archive.extractall(sys.argv[2]) | |
| PY | |
| local inner_zip | |
| inner_zip="$(find "$extracted_dir" -type f -name 'MesaFrame-portable.zip' | head -n 1)" | |
| [[ -n "$inner_zip" ]] || fail "nao encontrei o zip final dentro do artefato baixado" | |
| local final_zip="$output_dir/MesaFrame-portable-run-${run_id}.zip" | |
| cp "$inner_zip" "$final_zip" | |
| log "zip final salvo em: $final_zip" | |
| } | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --no-wait) | |
| WAIT_FOR_RUN=0 | |
| shift | |
| ;; | |
| --no-download) | |
| DOWNLOAD_ARTIFACT=0 | |
| shift | |
| ;; | |
| --download-dir) | |
| [[ $# -ge 2 ]] || fail "faltou valor para --download-dir" | |
| DOWNLOAD_DIR="$2" | |
| shift 2 | |
| ;; | |
| --remote) | |
| [[ $# -ge 2 ]] || fail "faltou valor para --remote" | |
| REMOTE="$2" | |
| shift 2 | |
| ;; | |
| --base-branch) | |
| [[ $# -ge 2 ]] || fail "faltou valor para --base-branch" | |
| BASE_BRANCH="$2" | |
| shift 2 | |
| ;; | |
| --windows-branch) | |
| [[ $# -ge 2 ]] || fail "faltou valor para --windows-branch" | |
| WINDOWS_BRANCH="$2" | |
| shift 2 | |
| ;; | |
| --workflow) | |
| [[ $# -ge 2 ]] || fail "faltou valor para --workflow" | |
| WORKFLOW_FILE="$2" | |
| shift 2 | |
| ;; | |
| --help|-h) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| fail "opcao desconhecida: $1" | |
| ;; | |
| esac | |
| done | |
| require_cmd git | |
| require_cmd curl | |
| require_cmd python3 | |
| ROOT_DIR="$(repo_root)" | |
| cd "$ROOT_DIR" | |
| MODE="$(auth_mode)" | |
| SLUG="$(repo_slug_from_remote)" | |
| ORIGINAL_BRANCH="$(git branch --show-current)" | |
| [[ -n "$DOWNLOAD_DIR" ]] || DOWNLOAD_DIR="$ROOT_DIR/release/windows-gh" | |
| ensure_branch_exists "$BASE_BRANCH" | |
| ensure_branch_exists "$WINDOWS_BRANCH" | |
| ensure_clean_tracked_tree | |
| git cat-file -e "${WINDOWS_BRANCH}:.github/workflows/${WORKFLOW_FILE}" 2>/dev/null || fail "workflow nao encontrado na branch '$WINDOWS_BRANCH': .github/workflows/$WORKFLOW_FILE" | |
| log "repositorio: $SLUG" | |
| log "branch base: $BASE_BRANCH" | |
| log "branch windows: $WINDOWS_BRANCH" | |
| git checkout "$WINDOWS_BRANCH" >/dev/null | |
| git merge --no-edit "$BASE_BRANCH" | |
| HEAD_SHA="$(git rev-parse HEAD)" | |
| log "push da branch $WINDOWS_BRANCH" | |
| git push "$REMOTE" "$WINDOWS_BRANCH:$WINDOWS_BRANCH" | |
| log "disparando workflow $WORKFLOW_FILE" | |
| github_api_post "actions/workflows/$WORKFLOW_FILE/dispatches" "$MODE" "$SLUG" "{\"ref\":\"$WINDOWS_BRANCH\"}" | |
| RUN_ID="$(find_run_id "$SLUG" "$MODE" "$HEAD_SHA")" | |
| log "run id: $RUN_ID" | |
| if (( WAIT_FOR_RUN == 1 )); then | |
| watch_run "$SLUG" "$MODE" "$RUN_ID" | |
| if (( DOWNLOAD_ARTIFACT == 1 )); then | |
| download_final_zip "$SLUG" "$MODE" "$RUN_ID" "$DOWNLOAD_DIR" | |
| fi | |
| else | |
| log "workflow disparado; como --no-wait foi usado, encerrando sem acompanhar" | |
| fi | |
| if [[ -n "$ORIGINAL_BRANCH" && "$ORIGINAL_BRANCH" != "$WINDOWS_BRANCH" ]]; then | |
| git checkout "$ORIGINAL_BRANCH" >/dev/null | |
| fi | |
| log "concluido" | |