Files
clawsec/scripts/release-skill.sh
T
David Abutbul d3c703aea6 ClawSec init
2026-02-05 21:58:23 +02:00

222 lines
7.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Usage: ./scripts/release-skill.sh <skill-name> <version>
# Example: ./scripts/release-skill.sh clawsec-feed 1.1.0
#
# This script ensures version consistency by:
# 1. Updating skill.json with the new version
# 2. Updating any hardcoded version URLs in skill.json and SKILL.md
# 3. Committing the changes
# 4. Creating the git tag
#
# After running, push with: git push && git push origin <tag>
set -euo pipefail
SKILL_NAME="$1"
VERSION="$2"
SKILL_PATH="skills/$SKILL_NAME"
# Validation
if [ -z "$SKILL_NAME" ] || [ -z "$VERSION" ]; then
echo "Usage: $0 <skill-name> <version>"
echo "Example: $0 clawsec-feed 1.1.0"
exit 1
fi
# Security: Validate skill name to prevent path injection
# Only allow lowercase alphanumeric characters and hyphens
if ! [[ "$SKILL_NAME" =~ ^[a-z0-9-]+$ ]]; then
echo "Error: Invalid skill name. Only lowercase alphanumeric characters and hyphens are allowed."
echo "Example: clawsec-feed, prompt-agent, clawtributor"
exit 1
fi
if [ ! -f "$SKILL_PATH/skill.json" ]; then
echo "Error: $SKILL_PATH/skill.json not found"
exit 1
fi
# Validate semver format (supports prerelease like 1.0.0-beta1)
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then
echo "Error: Invalid version format. Use semver (e.g., 1.0.0, 1.1.0-beta1)"
exit 1
fi
TAG="${SKILL_NAME}-v${VERSION}"
# Check if tag already exists
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Error: Tag $TAG already exists"
exit 1
fi
# Check for uncommitted changes in skill directory
if ! git diff --quiet "$SKILL_PATH/" 2>/dev/null; then
echo "Error: $SKILL_PATH/ has uncommitted changes. Please commit or stash them first."
exit 1
fi
echo "Releasing $SKILL_NAME version $VERSION"
echo "======================================="
# Create a temporary directory for atomic operations
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
# Track files that need to be staged
FILES_TO_STAGE=()
# Update version in skill.json
echo "Updating $SKILL_PATH/skill.json version to $VERSION..."
if ! jq --arg v "$VERSION" '.version = $v' "$SKILL_PATH/skill.json" > "$TEMP_DIR/skill.json"; then
echo "Error: Failed to update version in skill.json"
exit 1
fi
mv "$TEMP_DIR/skill.json" "$SKILL_PATH/skill.json"
FILES_TO_STAGE+=("$SKILL_PATH/skill.json")
# Update any hardcoded version URLs in skill.json (openclaw.feed_url pattern)
if jq -e '.openclaw.feed_url' "$SKILL_PATH/skill.json" >/dev/null 2>&1; then
echo "Updating openclaw.feed_url to use tag $TAG..."
if ! jq --arg tag "$TAG" '.openclaw.feed_url = (.openclaw.feed_url | gsub("/[^/]+-v[0-9.]+(-[a-zA-Z0-9]+)?/"; "/\($tag)/"))' "$SKILL_PATH/skill.json" > "$TEMP_DIR/skill.json"; then
echo "Error: Failed to update feed_url in skill.json"
exit 1
fi
mv "$TEMP_DIR/skill.json" "$SKILL_PATH/skill.json"
fi
# Update version in SKILL.md frontmatter and ALL hardcoded version URLs (if file exists)
if [ -f "$SKILL_PATH/SKILL.md" ]; then
echo "Updating $SKILL_PATH/SKILL.md frontmatter version to $VERSION..."
# Verify version line exists before sed
if ! grep -qE "^version: " "$SKILL_PATH/SKILL.md"; then
echo "Error: SKILL.md missing 'version:' line in frontmatter" >&2
echo " Expected format: 'version: X.Y.Z' at start of line" >&2
exit 1
fi
# Apply sed and verify substitution occurred
sed "s/^version: .*/version: $VERSION/" "$SKILL_PATH/SKILL.md" > "$TEMP_DIR/SKILL.md"
if ! grep -qF "version: $VERSION" "$TEMP_DIR/SKILL.md"; then
echo "Error: Failed to update version in SKILL.md frontmatter" >&2
echo " Target version: $VERSION" >&2
exit 1
fi
echo " ✓ Version updated to $VERSION"
echo "Updating hardcoded version URLs in SKILL.md to use tag $TAG..."
# Replace all hardcoded version URLs: download/SKILLNAME-vX.Y.Z(-prerelease)?/ -> download/TAG/
# This handles patterns like: download/clawsec-feed-v1.0.0/ or download/prompt-agent-v1.0.0-beta1/
PATTERN="/download/${SKILL_NAME}-v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?/"
# Check if pattern exists (warn if not, don't fail - some skills may not self-reference)
if grep -qE "$PATTERN" "$TEMP_DIR/SKILL.md"; then
sed -E "s|$PATTERN|/download/${TAG}/|g" "$TEMP_DIR/SKILL.md" > "$TEMP_DIR/SKILL.md.tmp"
# Verify substitution occurred
if ! grep -qF "/download/${TAG}/" "$TEMP_DIR/SKILL.md.tmp"; then
echo "Warning: URL pattern found but substitution may have failed" >&2
else
URL_COUNT=$(grep -cF "/download/${TAG}/" "$TEMP_DIR/SKILL.md.tmp")
echo " ✓ Updated $URL_COUNT hardcoded URL(s)"
fi
mv "$TEMP_DIR/SKILL.md.tmp" "$TEMP_DIR/SKILL.md"
else
echo " No hardcoded version URLs found (OK if skill doesn't self-reference)"
fi
mv "$TEMP_DIR/SKILL.md" "$SKILL_PATH/SKILL.md"
FILES_TO_STAGE+=("$SKILL_PATH/SKILL.md")
fi
# Update hardcoded version URLs in other markdown files (heartbeat.md, reporting.md, etc.)
for md_file in "$SKILL_PATH"/*.md; do
if [ -f "$md_file" ] && [ "$md_file" != "$SKILL_PATH/SKILL.md" ]; then
filename=$(basename "$md_file")
echo "Updating hardcoded version URLs in $filename..."
PATTERN="/download/${SKILL_NAME}-v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?/"
# Check if pattern exists
if grep -qE "$PATTERN" "$md_file"; then
sed -E "s|$PATTERN|/download/${TAG}/|g" "$md_file" > "$TEMP_DIR/$filename"
# Verify substitution occurred
if ! grep -qF "/download/${TAG}/" "$TEMP_DIR/$filename"; then
echo " Warning: URL pattern found but substitution may have failed in $filename" >&2
else
URL_COUNT=$(grep -cF "/download/${TAG}/" "$TEMP_DIR/$filename")
echo " ✓ Updated $URL_COUNT URL(s) in $filename"
fi
mv "$TEMP_DIR/$filename" "$md_file"
FILES_TO_STAGE+=("$md_file")
else
echo " No hardcoded version URLs found in $filename (skipping)"
fi
fi
done
# Show what changed
echo ""
echo "Changes to $SKILL_PATH/:"
git diff "$SKILL_PATH/" || true
echo ""
# Stage all changed files atomically
echo "Staging changes..."
for file in "${FILES_TO_STAGE[@]}"; do
git add "$file"
done
# Verify staged changes before committing
if git diff --cached --quiet; then
echo "Warning: No changes to commit"
exit 0
fi
# Commit the version bump
echo "Committing changes..."
if ! git commit -m "chore($SKILL_NAME): bump version to $VERSION"; then
echo "Error: Failed to commit changes"
exit 1
fi
# Save commit SHA for recovery (in case tag creation fails)
COMMIT_SHA=$(git rev-parse HEAD)
echo "Committed: $COMMIT_SHA"
# Create annotated tag
echo "Creating tag: $TAG"
if ! git tag -a "$TAG" -m "$SKILL_NAME version $VERSION"; then
echo "Error: Failed to create tag $TAG" >&2
echo "" >&2
echo "The commit has been created but NOT tagged:" >&2
echo " Commit: $COMMIT_SHA" >&2
echo "" >&2
echo "Recovery options:" >&2
echo " 1. Fix the issue and tag manually:" >&2
echo " git tag -a '$TAG' -m '$SKILL_NAME version $VERSION' $COMMIT_SHA" >&2
echo "" >&2
echo " 2. Investigate why tagging failed:" >&2
echo " - Check if tag exists: git tag -l '$TAG'" >&2
echo " - Check permissions: ls -ld .git/refs/tags" >&2
echo "" >&2
echo " 3. To rollback the commit (if desired):" >&2
echo " git reset --hard HEAD~1" >&2
echo "" >&2
echo "The commit has NOT been pushed. Fix the issue before pushing." >&2
exit 1
fi
echo ""
echo "Done! To release, push the commit and tag:"
echo " git push && git push origin $TAG"
echo ""
echo "Or to undo:"
echo " git reset --hard HEAD~1 && git tag -d $TAG"