SuperAPI AI Directory

lovstudio-release-via-cicd

Deployment & operations

View repository

Updated 10-16 00:01

About this skill

Release via CI/CD

Idempotent, adaptive release process.By default, automatically run setup + publish。

By default, use changesets, unless the user explicitly chooses to keep semantic-release.

Step 0: Opening joke 🎭

Before starting the release, tell a programmer joke to lighten the mood:

Randomly choose one of the following jokes to tell:

  1. Why do programmers always confuse Halloween and Christmas? Because Oct 31 = Dec 25

  2. What is the number programmers hate most? 2.0—because it means a rewrite

  3. "My code runs!" "Great, commit it." "Wait, let me first see why it runs..."

  4. Product manager: This requirement is simple. Programmer: That sentence itself is very complex.

  5. Why do programmers like dark mode? Because bugs are afraid of light

  6. git commit -m "final version" → git commit -m "final version 2" → git commit -m "this time it's really the final version"

  7. 99 bugs in the code, 99 bugs~ fix one, compile it, 127 bugs in the code...

After telling the joke, continue executing the release process.

Parameters

无参数      → setup + publish(默认,自动模式)
setup      → 仅检查/修复配置
publish    → 仅执行发布
--keep-semantic-release  → 保留现有 semantic-release 配置
patch|minor|major        → 指定版本类型

Automatic mode behavior

Principle: execute automatically unless very uncertain

  1. Auto commit: when there are uncommitted changes, automatically git add -A && git commit

    • Commit message inferred from diff content (e.g. "fix: update xxx" or "feat: add xxx")
    • Only ask when the changes are complex and the intent cannot be inferred
  2. Auto version: default patch, unless the user explicitly specifies otherwise

    • First release (no tag) → 0.1.0(not 1.0.0! 1.0+ requires explicit user specification)
    • Current version 0.x.y → keep the 0.x prefix, increment by patch/minor
    • Default → patch (the safest choice)
    • User parameter minor → minor
    • User parameter major → major
    • User explicitly specifies 1.0.0 or major and current ≥ 0.x → only then can it be upgraded to 1.0+
    • Detected BREAKING change → ask the user whether to use major

    Version number philosophy: 0.x means "features are continuously evolving", 1.0 means "stable API commitment". Never jump to 1.0+ on your own without user guidance. If there is already an incorrect 1.0+ tag, propose a rewrite (delete the old tag/release, restart from 0.x).

  3. Conditions for asking: only ask the user in the following cases

    • The workspace has multiple unrelated changes
    • The changes involve sensitive files (e.g. .env, secrets)
    • The version type cannot be automatically inferred (e.g. refactor may be patch or minor)
  4. Automatic branch handling: if on a feature branch

    • Automatically commit the current changes
    • Automatically push to remote
    • Automatically switch to main and merge the feature branch
    • Use --no-ff to preserve branch history
  5. Automatic Issue handling: detect related issues from branch name/commit

    • Branch name format:*/issue-<number>* or */<number>-*
    • Commit format:Closes #<number> / Fixes #<number> / Resolves #<number>
    • Automatically comment + close after successful publish

Step 1: Automatic project detection

类型: Tauri (src-tauri/) | Monorepo (pnpm-workspace.yaml) | Node (package.json) | Shell (无 package.json)
目标: npm | GitHub Release | 二进制
子类型:
  - Obsidian 插件 → GitHub Release Only(tag 触发,无 npm)
  - Vite/前端项目 (private: true) → GitHub Release + dist.zip
发布工具:
  - 检测到 semantic-release → 询问是否迁移到 changesets(推荐)
  - 检测到 changesets → 继续使用
  - 未配置 → 自动配置 changesets

Shell project: Only needs workflow + tag + CHANGELOG.md Vite/frontend project: GitHub Release + upload {project}-{version}.zip

All project types: CHANGELOG.md must be maintained; before publishing, automatically add the new version's change record at the top, and the workflow extracts release notes from it. Do not use generate_release_notes: true。

semantic-release migration detection

Detect whether semantic-release is used:

# 检查 package.json 是否有 semantic-release 相关配置
grep -q "semantic-release" package.json && echo "semantic-release detected"
# 检查 workflow 是否调用 semantic-release
grep -rq "semantic-release" .github/workflows/ && echo "workflow uses semantic-release"

If semantic-release is detected and --keep-semantic-release:

  1. Use AskUserQuestion to ask the user whether to migrate to changesets
  2. Recommend migration (changesets is more flexible and supports Monorepo)
  3. After the user agrees, perform the migration (see "Migration steps" below)

Steps to migrate to changesets

# 1. 初始化 changesets
pnpm add -D @changesets/cli
pnpm changeset init

# 2. 更新 .changeset/config.json
cat > .changeset/config.json << 'EOF'
{
  "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}
EOF

# 3. 移除 semantic-release 配置
# - 从 package.json 删除 "release" 配置块
# - 从 devDependencies 删除 semantic-release 相关包

# 4. 更新 workflow(见下方 Workflow 模板)

Setup stage

Check and report

✓/✗ .github/workflows/release.yml
✓/✗ .github/workflows/release.yml 包含 `permissions: contents: write`
✓/✗ Repo workflow permissions (write)
✓/✗ [Node] package.json (packageManager, scripts)
✓/✗ [Node] package.json 包含 packageManager 字段
✓/✗ [Node] .changeset/config.json(推荐)
⚠️ [Node] semantic-release 检测(建议迁移到 changesets)
✓/✗ [npm] NPM_TOKEN secret
✓/✗ CHANGELOG.md 存在且格式正确(## x.y.z 格式)
✓/✗ [Tauri] Cargo.toml 版本同步

Auto-fix

Repo permissions:

REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
gh api "repos/${REPO}/actions/permissions/workflow" -X PUT \
  -f default_workflow_permissions=write -F can_approve_pull_request_reviews=true

packageManager field(if missing):

# 按 lockfile/现有字段检测,不要强行改成 pnpm
if node -e "process.exit(require('./package.json').packageManager ? 0 : 1)" 2>/dev/null; then
  node -p "require('./package.json').packageManager"
elif [ -f bun.lock ] || [ -f bun.lockb ]; then
  BUN_VERSION=$(bun --version)
  # 在 package.json 中添加 "packageManager": "bun@${BUN_VERSION}"
elif [ -f pnpm-lock.yaml ]; then
  PNPM_VERSION=$(pnpm --version)
  # 在 package.json 中添加 "packageManager": "pnpm@${PNPM_VERSION}"
elif [ -f yarn.lock ]; then
  YARN_VERSION=$(yarn --version)
  # 在 package.json 中添加 "packageManager": "yarn@${YARN_VERSION}"
else
  NPM_VERSION=$(npm --version)
  # 在 package.json 中添加 "packageManager": "npm@${NPM_VERSION}"
fi

Shell project Workflow: Template see references/general-release-playbooks.md. Shell projects must also support workflow_dispatch, and extract release notes from CHANGELOG.md .

Node project principles:

  • Must support workflow_dispatch
  • Tauri uses job chaining (GITHUB_TOKEN limitation)
  • npm packages need NPM_TOKEN
  • The workflow must choose bun/pnpm/yarn/npm according to packageManager ; do not hardcode pnpm
  • Bun projects use oven-sh/setup-bun@v2、bun install --frozen-lockfile、bun run build
  • pnpm projects use pnpm/action-setup@v4,Do not specify version(read the packageManager field)
  • Tauri macOS cross-compilation requires adding Rust targets: aarch64-apple-darwin,x86_64-apple-darwin

CHANGELOG.md integration:

  • When publishing, extract the corresponding version content from CHANGELOG.md as release notes
  • Fallback: Use GitHub auto-generation when CHANGELOG.md has no content
  • Avoid generate_release_notes being repeated in multiple jobs

Publish stage

Pre-checks

git status --porcelain      # 不干净 → 自动提交(见自动模式)
git branch --show-current   # 非 main/master → 自动合并(见分支处理)
git pull --rebase
# CHANGELOG.md 检查:不存在 → 自动创建(从 git log 生成历史记录)
# 发布前必须在 CHANGELOG.md 顶部添加新版本变更记录

Prevent drift in long workflows:

  • Before creating the release commit and tag, must confirm that all diffs to be published in this round have been committed.
  • git tag then record RELEASE_COMMIT=$(git rev-parse HEAD); the workflow, release, and verification all correspond only to this commit.
  • If the user continues to modify the workspace while the workflow is running, after publishing is complete only do a git status report; do not add new dirty changes to the already tagged version.
  • If these new changes must be included, bump a new version again; do not silently move an already published/announced tag.

Automatic branch merge

BRANCH=$(git branch --show-current)
MAIN_BRANCH="main"  # 或 master,自动检测

# 如果不在 main 分支
if [ "$BRANCH" != "$MAIN_BRANCH" ]; then
  # 1. 提交并推送当前分支
  git add -A && git commit -m "<auto message>" || true
  git push origin "$BRANCH"

  # 2. 切换到 main 并拉取最新
  git checkout "$MAIN_BRANCH"
  git pull origin "$MAIN_BRANCH"

  # 3. 合并 feature 分支(保留历史)
  git merge "$BRANCH" --no-ff -m "Merge branch '$BRANCH' into $MAIN_BRANCH

<changeset description>

Closes #<issue_number>"  # 如果检测到关联 issue
fi

Automatic issue detection and handling

# 从分支名提取 issue 号
BRANCH=$(git branch --show-current)
ISSUE_NUM=""

# 匹配模式:issue-123, 123-feature, feature/issue-123
if [[ "$BRANCH" =~ issue-([0-9]+) ]]; then
  ISSUE_NUM="${BASH_REMATCH[1]}"
elif [[ "$BRANCH" =~ ^([0-9]+)- ]]; then
  ISSUE_NUM="${BASH_REMATCH[1]}"
elif [[ "$BRANCH" =~ /([0-9]+)- ]]; then
  ISSUE_NUM="${BASH_REMATCH[1]}"
fi

# 从 commit message 提取(作为补充)
if [ -z "$ISSUE_NUM" ]; then
  COMMITS=$(git log "$MAIN_BRANCH"..HEAD --format=%s 2>/dev/null)
  ISSUE_NUM=$(echo "$COMMITS" | grep -oE '(Closes|Fixes|Resolves) #[0-9]+' | head -1 | grep -oE '[0-9]+')
fi

# 验证 issue 存在且未关闭
if [ -n "$ISSUE_NUM" ]; then
  STATE=$(gh issue view "$ISSUE_NUM" --json state -q '.state' 2>/dev/null || echo "")
  if [ "$STATE" = "OPEN" ]; then
    echo "检测到关联 Issue #$ISSUE_NUM"
  else
    ISSUE_NUM=""  # 忽略已关闭的 issue
  fi
fi

Automatically close issues after publishing

# 在 workflow 成功后执行
if [ -n "$ISSUE_NUM" ]; then
  VERSION="v${VERSION}"
  RELEASE_URL="https://github.com/${REPO}/releases/tag/${VERSION}"

  # 添加评论
  gh issue comment "$ISSUE_NUM" --body "已在 ${VERSION} 中修复。

Release: ${RELEASE_URL}"

  # 关闭 issue
  gh issue close "$ISSUE_NUM" --reason completed

  echo "✓ Issue #$ISSUE_NUM 已关闭"
fi

Auto-commit logic

# 1. 检查变更
CHANGES=$(git status --porcelain)
if [ -z "$CHANGES" ]; then exit; fi

# 2. 分析变更推断提交类型
#    - 修改现有文件 → fix
#    - 添加新文件 → feat
#    - 删除文件 → chore
#    - 配置文件 → chore

# 3. 推断提交描述(从文件名/diff 内容)
#    - 单文件:直接用文件名
#    - 多文件同类:归纳共同点
#    - 复杂变更:才询问用户

# 4. 自动提交
git add -A && git commit -m "${TYPE}: ${DESC}"

Automatic version inference

# 版本类型优先级:
# 1. 用户显式参数 (patch/minor/major/具体版本号)
# 2. 检测到 BREAKING 变更 → 询问用户
# 3. 无 tag(首次发布)→ v0.1.0(不是 v1.0.0!)
# 4. 当前 0.x → 保持 0.x,按 patch/minor 递增
# 5. 默认 patch(最安全)
#
# ⚠️ 1.0+ 需要用户明确指定,绝不自行跳到 1.0+

LATEST=$(git tag -l 'v*' | sort -V | tail -1)
if [ -z "$LATEST" ]; then
  NEXT="v0.1.0"  # 首次发布从 0.1.0 开始
else
  # 解析当前版本并递增
  # 0.1.0 + patch → 0.1.1
  # 0.1.1 + minor → 0.2.0
  # 只有用户显式指定 major 或 1.0.0 才跳到 1.x
fi

# 检查是否有 BREAKING 变更
LAST_MSG=$(git log -1 --format=%s)
if [[ "$LAST_MSG" =~ ^BREAKING ]] || [[ "$LAST_MSG" =~ ^major: ]]; then
  echo "检测到可能的 BREAKING CHANGE: $LAST_MSG"
  echo "是否使用 major 版本?(y/N)"
  # 使用 AskUserQuestion 工具询问
fi

VERSION_TYPE="${USER_SPECIFIED_VERSION:-patch}"

Shell projects

# 获取最新 tag 并递增
LATEST=$(git tag -l 'v*' | sort -V | tail -1)
NEXT="v0.1.0"  # 无 tag 时默认 0.1.0(不是 1.0.0!)
# 自动递增 patch(0.1.0 → 0.1.1),minor 需用户指定

git tag "$NEXT" && git push --tags
# workflow 自动触发

Node projects

Automatic mode(default):

  • Version: automatically inferred from commit type (see automatic version inference)
  • Method: default local(fastest)

Ask mode(only when inference is not possible):

版本: [patch] / [minor] / [major]
方式: [local] 快速本地 | [ci] 通过 PR | [ci-auto] PR+自动合并

Local path (Tauri):

# 1. 创建 changeset(如缺失)
cat > .changeset/<name>.md << 'EOF'
---
"<package>": patch
---

<description>
EOF

# 2. Bump version
pnpm changeset version  # 或项目自定义的 changeset:version 脚本
git add . && git commit -m "chore: release v${VERSION}"
git push

# 3. 创建并推送 tag(workflow checkout 需要 tag 存在)
git tag v${VERSION} && git push origin v${VERSION}

# 4. 触发构建
gh workflow run release.yml -f tag=v${VERSION}
sleep 3
RUN_ID=$(gh run list -w release.yml -L 1 --json databaseId -q '.[0].databaseId')
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
echo "Workflow: https://github.com/$REPO/actions/runs/$RUN_ID"

# 5. **必须**等待 workflow 完成(指数退避轮询)
gh_view_json() {
  local run_id="$1"
  local json="$2"
  local query="$3"
  local attempt=1
  while [ "$attempt" -le 5 ]; do
    if gh run view "$run_id" --json "$json" -q "$query"; then
      return 0
    fi
    sleep $((attempt * 2))
    attempt=$((attempt + 1))
  done
  return 1
}

DELAY=5
MAX_DELAY=60
while true; do
  STATUS=$(gh_view_json "$RUN_ID" status '.status') || {
    echo "GitHub API 暂时不可用,下一轮继续重试..."
    sleep "$DELAY"
    continue
  }
  if [ "$STATUS" = "completed" ]; then
    CONCLUSION=$(gh_view_json "$RUN_ID" conclusion '.conclusion' || echo "unknown")
    echo "Workflow $CONCLUSION"
    break
  fi
  echo "Status: $STATUS, waiting ${DELAY}s..."
  sleep $DELAY
  DELAY=$((DELAY * 2 > MAX_DELAY ? MAX_DELAY : DELAY * 2))
done

Local path (pure Node):

pnpm build && pnpm changeset publish
git push --follow-tags
NOTES=$(awk -v ver="${VERSION}" '/^## / { if (found) exit; if ($2 == ver) { found=1; next } } found { print }' CHANGELOG.md)
gh release create "v${VERSION}" --notes "${NOTES:-Release v${VERSION}}" --latest

CI path:

git add -A && git commit -m "chore: add changeset" && git push
# 等待 Version Packages PR
gh pr merge ... --squash --delete-branch  # ci-auto

References

  • Tauri/macOS signing/notarization/Windows fallback: references/tauri-release-workflow.md
  • Shell, Vite, monorepo templates, workflow monitoring, README/Vercel audits, issue automation, and failure recovery: references/general-release-playbooks.md

When a referenced template conflicts with this SKILL.md, follow the stricter rule: maintain CHANGELOG.md, preserve package manager choice, do not print secrets, wait for workflow completion with retry, and verify final release assets before reporting success.