Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/cli/src/commands/core/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ pub fn tag_exists(path: &Path, tag: &str) -> bool {
.unwrap_or(false)
}

/// Fetch tags from remote to sync local tags with remote
pub fn fetch_tags(path: &Path, remote: &str) -> Result<()> {
git_cmd(&["fetch", remote, "--tags", "--force"], path)?;
Ok(())
}

/// List all tags sorted by version (newest first)
pub fn list_tags(path: &Path) -> Result<Vec<String>> {
let output = git_cmd(&["tag", "-l", "--sort=-v:refname"], path)?;
Expand Down
24 changes: 21 additions & 3 deletions apps/cli/src/commands/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ fn filter_semver_tags(tags: &[String]) -> Vec<String> {
.collect()
}

/// Get the latest semver version from a list of tags
fn get_latest_version(tags: &[String]) -> Option<String> {
filter_semver_tags(tags).into_iter().next()
}

/// Prompt user to select version bump type, existing tag, or enter custom version
fn prompt_tag_selection(existing_tags: &[String], current_version: &str) -> Result<TagSelection> {
// Filter to only semver tags
Expand Down Expand Up @@ -255,8 +260,19 @@ pub async fn run(args: PublishArgs) -> Result<()> {
}

// Step 4: Determine which tag to use
// First, sync local tags with remote to ensure we have the latest state from GitHub
print!(" Syncing tags from origin... ");
match git::fetch_tags(&skill_path, remote) {
Ok(()) => println!("✓"),
Err(e) => println!("⚠ ({})", e),
}

// Now list local tags (which are synced with remote)
let existing_tags = git::list_tags(&skill_path)?;

// Get the latest version from tags for bump calculations
let latest_version = get_latest_version(&existing_tags);

let (tag, needs_create) = if let Some(explicit_tag) = args.tag.clone() {
// User explicitly provided a tag via --tag flag - validate it's semver
let tag_to_check = if explicit_tag.starts_with('v') {
Expand All @@ -271,8 +287,9 @@ pub async fn run(args: PublishArgs) -> Result<()> {
}
(tag_to_check, false)
} else if args.yes {
// Non-interactive mode: create patch bump
let (major, minor, patch) = parse_version(current_version)?;
// Non-interactive mode: create patch bump based on latest tag version
let base_version = latest_version.as_deref().unwrap_or(current_version);
let (major, minor, patch) = parse_version(base_version)?;
let new_tag = format!("v{}.{}.{}", major, minor, patch + 1);
if git::tag_exists(&skill_path, &new_tag) {
bail!("Tag {} already exists.", new_tag);
Expand All @@ -281,7 +298,8 @@ pub async fn run(args: PublishArgs) -> Result<()> {
} else {
// Interactive mode: let user choose bump type or existing tag
println!();
match prompt_tag_selection(&existing_tags, current_version)? {
let base_version = latest_version.as_deref().unwrap_or(current_version);
match prompt_tag_selection(&existing_tags, base_version)? {
TagSelection::New(tag) => {
if git::tag_exists(&skill_path, &tag) {
bail!("Tag {} already exists.", tag);
Expand Down