From 75d66cd47c0e2c197bd4ea9f0b03d6cfb601b562 Mon Sep 17 00:00:00 2001 From: cinco euzebio Date: Sun, 1 Mar 2026 04:50:52 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20publish=20releases=20to=20G?= =?UTF-8?q?itHub=20and=20update=20install=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Self-update now queries the GitHub Releases API (parse_latest_tag extracted for testability) - install.sh now fetches and downloads from GitHub Releases - Drone CI release pipeline publishes to both Gitea and GitHub via GITHUB_TOKEN secret - Bump version to 0.7.0 --- .drone.yml | 40 +++++++++++++++++++++++++++++++++ CHANGELOG.md | 7 ++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- install.sh | 11 ++++++---- src/self_update.rs | 55 ++++++++++++++++++++++++++++++++-------------- 6 files changed, 95 insertions(+), 22 deletions(-) diff --git a/.drone.yml b/.drone.yml index 8c1cb80..862f524 100644 --- a/.drone.yml +++ b/.drone.yml @@ -109,3 +109,43 @@ steps: depends_on: - build-x86_64 - build-aarch64 + + - name: publish-github + image: alpine + environment: + GITHUB_TOKEN: + from_secret: GITHUB_TOKEN + GITHUB_REPO: cinco/tmuxido + commands: + - apk add --no-cache curl jq + - TAG=${DRONE_TAG} + - | + NOTES=$(awk "/^## \[$TAG\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md) + - | + EXISTING=$(curl -fsSL \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$GITHUB_REPO/releases/tags/$TAG" | jq -r '.id // empty') + if [ -n "$EXISTING" ]; then + curl -s -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPO/releases/$EXISTING" + fi + - | + RELEASE_ID=$(curl -fsSL -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/$GITHUB_REPO/releases" \ + -d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":$(echo "$NOTES" | jq -Rs .)}" \ + | jq -r '.id') + - | + for ARCH in x86_64 aarch64; do + curl -fsSL -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + "https://uploads.github.com/repos/$GITHUB_REPO/releases/$RELEASE_ID/assets?name=tmuxido-${ARCH}-linux" \ + --data-binary @"tmuxido-${ARCH}-linux" + done + depends_on: + - build-x86_64 + - build-aarch64 diff --git a/CHANGELOG.md b/CHANGELOG.md index 26d6b7d..d3cbe9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.7.0] - 2026-03-01 + +### Changed +- `install.sh` now downloads from GitHub Releases +- Self-update now queries the GitHub Releases API for new versions +- Releases are published to both Gitea and GitHub + ## [0.6.0] - 2026-03-01 ### Added diff --git a/Cargo.lock b/Cargo.lock index 76442d2..bc4e7b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -864,7 +864,7 @@ dependencies = [ [[package]] name = "tmuxido" -version = "0.6.0" +version = "0.7.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 1d96e40..b4c87fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tmuxido" -version = "0.6.0" +version = "0.7.0" edition = "2024" [dev-dependencies] diff --git a/install.sh b/install.sh index ae2e61b..be7c1d9 100644 --- a/install.sh +++ b/install.sh @@ -1,8 +1,9 @@ #!/bin/sh set -e -REPO="cinco/Tmuxido" -BASE_URL="https://git.cincoeuzebio.com" +REPO="cinco/tmuxido" +BASE_URL="https://github.com" +API_URL="https://api.github.com" INSTALL_DIR="$HOME/.local/bin" arch=$(uname -m) @@ -12,8 +13,10 @@ case "$arch" in *) echo "Unsupported architecture: $arch" >&2; exit 1 ;; esac -tag=$(curl -fsSL "$BASE_URL/api/v1/repos/$REPO/releases?limit=1&page=1" \ - | grep -o '"tag_name":"[^"]*"' | head -1 | cut -d'"' -f4) +tag=$(curl -fsSL \ + -H "Accept: application/vnd.github.v3+json" \ + "$API_URL/repos/$REPO/releases/latest" \ + | grep -o '"tag_name":"[^"]*"' | cut -d'"' -f4) [ -z "$tag" ] && { echo "Could not fetch latest release" >&2; exit 1; } diff --git a/src/self_update.rs b/src/self_update.rs index e2099e9..6bae772 100644 --- a/src/self_update.rs +++ b/src/self_update.rs @@ -3,8 +3,9 @@ use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; use std::process::Command; -const REPO: &str = "cinco/Tmuxido"; -const BASE_URL: &str = "https://git.cincoeuzebio.com"; +const REPO: &str = "cinco/tmuxido"; +const BASE_URL: &str = "https://github.com"; +const API_BASE: &str = "https://api.github.com"; /// Check if running from cargo (development mode) fn is_dev_build() -> bool { @@ -26,12 +27,27 @@ fn detect_arch() -> Result<&'static str> { } } -/// Fetch latest release tag from Gitea API +/// Parse tag_name from a GitHub releases/latest JSON response +fn parse_latest_tag(response: &str) -> Result { + let tag: serde_json::Value = + serde_json::from_str(response).context("Failed to parse release API response")?; + tag.get("tag_name") + .and_then(|t| t.as_str()) + .map(|t| t.to_string()) + .ok_or_else(|| anyhow::anyhow!("Could not extract tag_name from release")) +} + +/// Fetch latest release tag from GitHub API pub(crate) fn fetch_latest_tag() -> Result { - let url = format!("{}/api/v1/repos/{}/releases?limit=1&page=1", BASE_URL, REPO); + let url = format!("{}/repos/{}/releases/latest", API_BASE, REPO); let output = Command::new("curl") - .args(["-fsSL", &url]) + .args([ + "-fsSL", + "-H", + "Accept: application/vnd.github.v3+json", + &url, + ]) .output() .context("Failed to execute curl. Make sure curl is installed.")?; @@ -42,17 +58,7 @@ pub(crate) fn fetch_latest_tag() -> Result { )); } - let response = String::from_utf8_lossy(&output.stdout); - - // Parse JSON response to extract tag_name - let tag: serde_json::Value = - serde_json::from_str(&response).context("Failed to parse release API response")?; - - tag.get(0) - .and_then(|r| r.get("tag_name")) - .and_then(|t| t.as_str()) - .map(|t| t.to_string()) - .ok_or_else(|| anyhow::anyhow!("Could not extract tag_name from release")) + parse_latest_tag(&String::from_utf8_lossy(&output.stdout)) } /// Get path to current executable @@ -211,6 +217,23 @@ mod tests { ); } + #[test] + fn should_parse_tag_from_github_latest_release_response() { + let json = r#"{"tag_name":"0.7.0","name":"0.7.0","body":"release notes"}"#; + assert_eq!(parse_latest_tag(json).unwrap(), "0.7.0"); + } + + #[test] + fn should_return_error_when_tag_name_missing() { + let json = r#"{"name":"0.7.0","body":"no tag_name field"}"#; + assert!(parse_latest_tag(json).is_err()); + } + + #[test] + fn should_return_error_when_response_is_invalid_json() { + assert!(parse_latest_tag("not valid json").is_err()); + } + #[test] fn should_compare_versions_correctly() { assert_eq!(