mirror of
https://github.com/yan-istart/IStart-Note-AI-Plugin.git
synced 2026-07-22 17:10:27 +00:00
321 lines
10 KiB
Bash
Executable file
321 lines
10 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# =============================================================================
|
||
# release.sh — Create a GitHub Release for the Obsidian plugin
|
||
#
|
||
# Usage:
|
||
# ./scripts/release.sh [version]
|
||
# ./scripts/release.sh --current # Release current version (no bump, no commit)
|
||
#
|
||
# Examples:
|
||
# ./scripts/release.sh 1.6.0 # Release version 1.6.0
|
||
# ./scripts/release.sh patch # Bump patch version (1.5.0 → 1.5.1)
|
||
# ./scripts/release.sh minor # Bump minor version (1.5.0 → 1.6.0)
|
||
# ./scripts/release.sh major # Bump major version (1.5.0 → 2.0.0)
|
||
# ./scripts/release.sh --current # Build + tag + release current manifest version
|
||
# ./scripts/release.sh # Interactive prompt
|
||
#
|
||
# Prerequisites:
|
||
# - Node.js & npm installed
|
||
# - GitHub CLI (gh) installed and authenticated: https://cli.github.com
|
||
# - Git remote "origin" points to your GitHub repository
|
||
# =============================================================================
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
|
||
# Colors
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
CYAN='\033[0;36m'
|
||
NC='\033[0m' # No Color
|
||
|
||
info() { echo -e "${CYAN}ℹ ${NC}$*"; }
|
||
ok() { echo -e "${GREEN}✔ ${NC}$*"; }
|
||
warn() { echo -e "${YELLOW}⚠ ${NC}$*"; }
|
||
error() { echo -e "${RED}✖ ${NC}$*" >&2; }
|
||
die() { error "$@"; exit 1; }
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Pre-flight checks
|
||
# ---------------------------------------------------------------------------
|
||
check_prerequisites() {
|
||
command -v node >/dev/null 2>&1 || die "Node.js is required but not installed."
|
||
command -v npm >/dev/null 2>&1 || die "npm is required but not installed."
|
||
command -v git >/dev/null 2>&1 || die "git is required but not installed."
|
||
command -v gh >/dev/null 2>&1 || die "GitHub CLI (gh) is required. Install: https://cli.github.com"
|
||
|
||
# Ensure gh is authenticated
|
||
gh auth status >/dev/null 2>&1 || die "GitHub CLI is not authenticated. Run: gh auth login"
|
||
|
||
# Ensure we are in a git repo with a remote
|
||
git -C "$PROJECT_DIR" remote get-url origin >/dev/null 2>&1 \
|
||
|| die "No git remote 'origin' found. Push your repo to GitHub first."
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Version helpers
|
||
# ---------------------------------------------------------------------------
|
||
get_current_version() {
|
||
node -e "console.log(require('$PROJECT_DIR/manifest.json').version)"
|
||
}
|
||
|
||
bump_version() {
|
||
local current="$1" part="$2"
|
||
IFS='.' read -r major minor patch <<< "$current"
|
||
case "$part" in
|
||
major) echo "$((major + 1)).0.0" ;;
|
||
minor) echo "${major}.$((minor + 1)).0" ;;
|
||
patch) echo "${major}.${minor}.$((patch + 1))" ;;
|
||
*) die "Invalid bump type: $part (use major, minor, or patch)" ;;
|
||
esac
|
||
}
|
||
|
||
validate_semver() {
|
||
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "Invalid semver: $1 (expected x.y.z)"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Update version in manifest.json, package.json, and versions.json
|
||
# ---------------------------------------------------------------------------
|
||
update_versions() {
|
||
local new_version="$1"
|
||
local min_app_version
|
||
|
||
min_app_version=$(node -e "console.log(require('$PROJECT_DIR/manifest.json').minAppVersion)")
|
||
|
||
info "Updating manifest.json → $new_version"
|
||
node -e "
|
||
const fs = require('fs');
|
||
const path = '$PROJECT_DIR/manifest.json';
|
||
const m = JSON.parse(fs.readFileSync(path, 'utf8'));
|
||
m.version = '$new_version';
|
||
fs.writeFileSync(path, JSON.stringify(m, null, 2) + '\n');
|
||
"
|
||
|
||
info "Updating package.json → $new_version"
|
||
node -e "
|
||
const fs = require('fs');
|
||
const path = '$PROJECT_DIR/package.json';
|
||
const p = JSON.parse(fs.readFileSync(path, 'utf8'));
|
||
p.version = '$new_version';
|
||
fs.writeFileSync(path, JSON.stringify(p, null, 2) + '\n');
|
||
"
|
||
|
||
info "Updating versions.json"
|
||
node -e "
|
||
const fs = require('fs');
|
||
const path = '$PROJECT_DIR/versions.json';
|
||
const v = JSON.parse(fs.readFileSync(path, 'utf8'));
|
||
v['$new_version'] = '$min_app_version';
|
||
fs.writeFileSync(path, JSON.stringify(v, null, 2) + '\n');
|
||
"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Build
|
||
# ---------------------------------------------------------------------------
|
||
build_plugin() {
|
||
if [[ ! -d "$PROJECT_DIR/node_modules" ]]; then
|
||
info "Installing dependencies..."
|
||
(cd "$PROJECT_DIR" && npm install)
|
||
fi
|
||
|
||
info "Building plugin (production)..."
|
||
(cd "$PROJECT_DIR" && npm run build)
|
||
|
||
# Verify build artifacts
|
||
[[ -f "$PROJECT_DIR/dist/main.js" ]] || die "Build failed: dist/main.js not found"
|
||
[[ -f "$PROJECT_DIR/dist/manifest.json" ]] || die "Build failed: dist/manifest.json not found"
|
||
|
||
ok "Build succeeded"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Git tag & push
|
||
# ---------------------------------------------------------------------------
|
||
create_tag() {
|
||
local version="$1"
|
||
|
||
# Stage and commit any uncommitted changes (source code, styles, etc.)
|
||
local has_changes=false
|
||
if [[ -n "$(git -C "$PROJECT_DIR" status --porcelain)" ]]; then
|
||
has_changes=true
|
||
fi
|
||
|
||
if [[ "$has_changes" == "true" ]]; then
|
||
info "Staging all changes..."
|
||
git -C "$PROJECT_DIR" add -A
|
||
|
||
# Show what will be committed
|
||
echo ""
|
||
git -C "$PROJECT_DIR" diff --cached --stat
|
||
echo ""
|
||
|
||
info "Committing: release $version"
|
||
git -C "$PROJECT_DIR" commit -m "release: $version"
|
||
fi
|
||
|
||
# Check if tag already exists
|
||
if git -C "$PROJECT_DIR" rev-parse "$version" >/dev/null 2>&1; then
|
||
die "Tag $version already exists. Delete it first or choose a different version."
|
||
fi
|
||
|
||
info "Creating git tag: $version"
|
||
git -C "$PROJECT_DIR" tag -a "$version" -m "$version"
|
||
|
||
info "Pushing commits and tag to origin..."
|
||
git -C "$PROJECT_DIR" push origin HEAD
|
||
git -C "$PROJECT_DIR" push origin "$version"
|
||
|
||
ok "Tag $version pushed"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Resolve owner/repo from git remote (gh --repo needs "owner/repo" format)
|
||
# ---------------------------------------------------------------------------
|
||
get_gh_repo() {
|
||
local remote_url
|
||
remote_url=$(git -C "$PROJECT_DIR" remote get-url origin)
|
||
echo "$remote_url" \
|
||
| sed -E 's#^https?://github\.com/##' \
|
||
| sed -E 's#^git@github\.com:##' \
|
||
| sed -E 's#\.git$##'
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Create GitHub Release
|
||
#
|
||
# Per Obsidian docs Step 2:
|
||
# - Tag version must match manifest.json version
|
||
# - Upload main.js, manifest.json (and styles.css if present) as binary
|
||
# attachments to the release
|
||
# ---------------------------------------------------------------------------
|
||
create_github_release() {
|
||
local version="$1"
|
||
local gh_repo
|
||
gh_repo=$(get_gh_repo)
|
||
|
||
# Obsidian expects bare filenames (main.js, manifest.json) as release assets.
|
||
# The build outputs to dist/, so we copy them to a temp dir to ensure the
|
||
# uploaded filenames are clean.
|
||
local staging
|
||
staging=$(mktemp -d)
|
||
|
||
cp "$PROJECT_DIR/dist/main.js" "$staging/main.js"
|
||
cp "$PROJECT_DIR/dist/manifest.json" "$staging/manifest.json"
|
||
|
||
local assets=("$staging/main.js" "$staging/manifest.json")
|
||
|
||
if [[ -f "$PROJECT_DIR/styles.css" ]]; then
|
||
cp "$PROJECT_DIR/styles.css" "$staging/styles.css"
|
||
assets+=("$staging/styles.css")
|
||
fi
|
||
|
||
info "Creating GitHub Release: $version (repo: $gh_repo)"
|
||
|
||
gh release create "$version" \
|
||
--repo "$gh_repo" \
|
||
--title "$version" \
|
||
--generate-notes \
|
||
"${assets[@]}"
|
||
|
||
rm -rf "$staging"
|
||
|
||
ok "Release created: $version"
|
||
echo ""
|
||
echo -e " ${CYAN}https://github.com/${gh_repo}/releases/tag/${version}${NC}"
|
||
echo ""
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Main
|
||
# ---------------------------------------------------------------------------
|
||
main() {
|
||
echo ""
|
||
echo -e "${CYAN}╔══════════════════════════════════════════════╗${NC}"
|
||
echo -e "${CYAN}║ Obsidian Plugin Release Script ║${NC}"
|
||
echo -e "${CYAN}╚══════════════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
|
||
check_prerequisites
|
||
|
||
local current_version new_version
|
||
current_version=$(get_current_version)
|
||
info "Current version: $current_version"
|
||
|
||
# --current mode: just build + tag + release, no version bump
|
||
if [[ "${1:-}" == "--current" ]]; then
|
||
new_version="$current_version"
|
||
echo ""
|
||
info "Release-only mode: using current version ${GREEN}$new_version${NC}"
|
||
echo ""
|
||
read -rp " Build and release $new_version? [y/N] " confirm
|
||
[[ "$confirm" =~ ^[Yy]$ ]] || { info "Aborted."; exit 0; }
|
||
echo ""
|
||
|
||
build_plugin
|
||
create_tag "$new_version"
|
||
create_github_release "$new_version"
|
||
|
||
echo -e "${GREEN}🎉 Release $new_version complete!${NC}"
|
||
echo ""
|
||
return
|
||
fi
|
||
|
||
# Determine new version
|
||
if [[ $# -ge 1 ]]; then
|
||
case "$1" in
|
||
major|minor|patch)
|
||
new_version=$(bump_version "$current_version" "$1")
|
||
;;
|
||
*)
|
||
new_version="$1"
|
||
;;
|
||
esac
|
||
else
|
||
echo ""
|
||
echo " How would you like to bump the version?"
|
||
echo ""
|
||
echo " 1) patch → $(bump_version "$current_version" patch)"
|
||
echo " 2) minor → $(bump_version "$current_version" minor)"
|
||
echo " 3) major → $(bump_version "$current_version" major)"
|
||
echo " 4) custom"
|
||
echo " 5) current (no bump, just release $current_version)"
|
||
echo ""
|
||
read -rp " Choose [1-5]: " choice
|
||
case "$choice" in
|
||
1) new_version=$(bump_version "$current_version" patch) ;;
|
||
2) new_version=$(bump_version "$current_version" minor) ;;
|
||
3) new_version=$(bump_version "$current_version" major) ;;
|
||
4) read -rp " Enter version (x.y.z): " new_version ;;
|
||
5) new_version="$current_version" ;;
|
||
*) die "Invalid choice" ;;
|
||
esac
|
||
fi
|
||
|
||
validate_semver "$new_version"
|
||
echo ""
|
||
info "New version: ${GREEN}$new_version${NC}"
|
||
echo ""
|
||
|
||
# Confirm
|
||
read -rp " Proceed with release $new_version? [y/N] " confirm
|
||
[[ "$confirm" =~ ^[Yy]$ ]] || { info "Aborted."; exit 0; }
|
||
echo ""
|
||
|
||
# Steps
|
||
if [[ "$new_version" != "$current_version" ]]; then
|
||
update_versions "$new_version"
|
||
fi
|
||
build_plugin
|
||
create_tag "$new_version"
|
||
create_github_release "$new_version"
|
||
|
||
echo -e "${GREEN}🎉 Release $new_version complete!${NC}"
|
||
echo ""
|
||
}
|
||
|
||
main "$@"
|