Ctrl K
ComunidadGuías…

scripts/changelog-extract.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/bash
# =============================================================================
# changelog-extract.sh — 从根 CHANGELOG.md 提取版本号与发版说明
#
# 用法:
#   changelog-extract.sh version [CHANGELOG]
#   changelog-extract.sh body <version> [--section 名称]... [CHANGELOG]
#   changelog-extract.sh ota-text <version> [CHANGELOG]
#   changelog-extract.sh apk-ota-text <version> [CHANGELOG]
#   changelog-extract.sh sync-desktop [CHANGELOG]
# =============================================================================
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
DEFAULT_CHANGELOG="${MAO_CHANGELOG:-$PROJECT_ROOT/CHANGELOG.md}"

# OTA 用户可见小节(前缀匹配 ### 标题)
OTA_SECTIONS=(
  "前端(桌面 / Web / 安卓)"
  "安卓原生"
)

usage() {
  sed -n '3,12p' "$0" | sed 's/^# //'
  exit 1
}

resolve_changelog() {
  local last="${*: -1}"
  if [[ $# -gt 0 && -f "$last" && "$last" == *.md ]]; then
    echo "$last"
  else
    echo "$DEFAULT_CHANGELOG"
  fi
}


cmd_version() {
  local file
  file="$(resolve_changelog "$@")"
  if [[ ! -f "$file" ]]; then
    echo "错误:CHANGELOG 不存在: $file" >&2
    exit 1
  fi
  grep -m1 '^## ' "$file" | sed 's/^## \([^ ]*\).*/\1/'
}

# body: 每行一条(已去掉 "- " 前缀)
cmd_body() {
  local version="$1"
  shift
  local sections=()
  local changelog_arg=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --section) sections+=("$2"); shift 2 ;;
      -h|--help) usage ;;
      *)
        if [[ -f "$1" && "$1" == *.md ]]; then
          changelog_arg="$1"
          shift
        else
          echo "未知参数: $1" >&2
          exit 1
        fi
        ;;
    esac
  done

  local CHANGELOG_FILE="${changelog_arg:-$DEFAULT_CHANGELOG}"

  local section_csv=""
  if ((${#sections[@]} > 0)); then
    section_csv=$(IFS='|'; echo "${sections[*]}")
  fi

  awk -v ver="$version" -v want="$section_csv" '
    function section_wanted(h,   i, n, p) {
      if (want == "") return 1
      n = split(want, parts, "|")
      for (i = 1; i <= n; i++) {
        p = parts[i]
        if (h == p || index(h, p) == 1) return 1
      }
      return 0
    }
    /^## / {
      if (in_ver) exit
      if ($0 ~ "^## " ver " ") {
        in_ver = 1
        has_sub = 0
        in_sec = 0
      }
      next
    }
    in_ver && /^## / { exit }
    in_ver && /^### / {
      has_sub = 1
      heading = $0
      sub(/^### /, "", heading)
      in_sec = section_wanted(heading)
      next
    }
    in_ver && /^- / {
      line = substr($0, 3)
      if (!has_sub) print line
      else if (in_sec) print line
    }
  ' "$CHANGELOG_FILE"
}

lines_to_pipe() {
  echo "$1" | sed '/^[[:space:]]*$/d' | tr '\n' '|' | sed 's/|$//'
}

# ota-text: pipe 分隔;有 ### 分节时仅取前端 + 安卓原生,否则取全部条目
cmd_ota_text() {
  local version="$1"
  shift
  local changelog_arg=""
  if [[ $# -ge 1 && -f "$1" && "$1" == *.md ]]; then
    changelog_arg="$1"
    shift
  fi
  local CHANGELOG_FILE="${changelog_arg:-$DEFAULT_CHANGELOG}"

  local lines
  lines=$(cmd_body "$version" --section "${OTA_SECTIONS[0]}" --section "${OTA_SECTIONS[1]}" "$CHANGELOG_FILE" || true)
  if [[ -z "$lines" ]]; then
    lines=$(cmd_body "$version" "$CHANGELOG_FILE")
  fi

  if [[ -z "$lines" ]]; then
    echo "错误:版本 $version 在 $CHANGELOG_FILE 中无 OTA 条目" >&2
    exit 1
  fi

  echo "$lines" | sed '/^[[:space:]]*$/d' | tr '\n' '|' | sed 's/|$//'
}

# apk-ota-text: 安卓壳 APK OTA 文案(优先 ### 安卓原生,否则回退 ota-text)
cmd_apk_ota_text() {
  local version="$1"
  shift
  local changelog_arg=""
  if [[ $# -ge 1 && -f "$1" && "$1" == *.md ]]; then
    changelog_arg="$1"
    shift
  fi
  local CHANGELOG_FILE="${changelog_arg:-$DEFAULT_CHANGELOG}"

  local lines
  lines=$(cmd_body "$version" --section "安卓原生" "$CHANGELOG_FILE" || true)
  if [[ -z "$lines" ]]; then
    cmd_ota_text "$version" "$CHANGELOG_FILE"
    return
  fi

  lines_to_pipe "$lines"
}

cmd_sync_desktop() {
  local file
  file="$(resolve_changelog "$@")"
  local version
  version="$(cmd_version "$file")"
  local pkg="$PROJECT_ROOT/desktop/package.json"

  if [[ ! -f "$pkg" ]]; then
    echo "错误:desktop/package.json 不存在" >&2
    exit 1
  fi

  node -e "
    const fs = require('fs');
    const p = process.argv[1];
    const ver = process.argv[2];
    const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
    if (pkg.version !== ver) {
      pkg.version = ver;
      fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n');
      console.log('desktop/package.json version -> ' + ver);
    }
    const lockPath = require('path').join(require('path').dirname(p), 'package-lock.json');
    if (!fs.existsSync(lockPath)) process.exit(0);
    const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
    let changed = false;
    if (lock.version !== ver) { lock.version = ver; changed = true; }
    if (lock.packages && lock.packages[''] && lock.packages[''].version !== ver) {
      lock.packages[''].version = ver;
      changed = true;
    }
    if (changed) {
      fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
      console.log('desktop/package-lock.json version -> ' + ver);
    }
  " "$pkg" "$version"
}

[[ $# -ge 1 ]] || usage

case "$1" in
  version) shift; cmd_version "$@" ;;
  body)
    shift
    [[ $# -ge 1 ]] || usage
    cmd_body "$@"
    ;;
  ota-text)
    shift
    [[ $# -ge 1 ]] || usage
    cmd_ota_text "$@"
    ;;
  apk-ota-text)
    shift
    [[ $# -ge 1 ]] || usage
    cmd_apk_ota_text "$@"
    ;;
  sync-desktop) shift; cmd_sync_desktop "$@" ;;
  -h|--help) usage ;;
  *) echo "未知命令: $1" >&2; usage ;;
esac