name: Release on: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+' workflow_dispatch: inputs: version: description: '要发布的版本号(不含 v 前缀,例如 1.0.1)' required: true notes: description: '发布说明(可选,留空自动生成 changelog)' required: false jobs: release: runs-on: ubuntu-latest steps: - name: 检出代码 uses: actions/checkout@v4 with: fetch-depth: 0 # 生成 changelog 需要完整历史 - name: 确定版本号 env: EVENT_NAME: ${{ github.event_name }} INPUT_VERSION: ${{ inputs.version }} run: | set -euo pipefail if [ "$EVENT_NAME" = "workflow_dispatch" ]; then VERSION="$INPUT_VERSION" else VERSION="${GITHUB_REF_NAME#v}" fi if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::版本号格式应为 X.Y.Z(收到:${VERSION:-空})" exit 1 fi echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "TAG=v$VERSION" >> "$GITHUB_ENV" echo "本次发版:v$VERSION(触发方式:$EVENT_NAME)" - name: 网页触发:同步 manifest 版本并打 tag if: ${{ github.event_name == 'workflow_dispatch' }} env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} ACTOR: ${{ github.actor }} run: | set -euo pipefail CUR=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])") if [ "$CUR" != "$VERSION" ]; then python3 - "$VERSION" <<'PY' import json, sys v = sys.argv[1] m = json.load(open('manifest.json')) m['version'] = v with open('manifest.json', 'w') as f: json.dump(m, f, ensure_ascii=False, indent=2) f.write('\n') PY git config user.name "${ACTOR}" git config user.email "${ACTOR}@users.noreply.${GITHUB_SERVER_URL#https://}" git add manifest.json git commit -m "release: v${VERSION} [skip ci]" echo "manifest.json 版本 $CUR -> $VERSION,已提交([skip ci] 防止重复触发流水线)" else echo "manifest.json 已是 $VERSION,无需提交" fi if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then TAG_SHA=$(git rev-parse "${TAG}^{commit}") HEAD_SHA=$(git rev-parse HEAD) if [ "$TAG_SHA" != "$HEAD_SHA" ]; then echo "::error::tag ${TAG} 已存在且指向其他提交,请先处理(删除旧 tag 或换版本号)" exit 1 fi echo "tag ${TAG} 已存在且指向当前提交,复用" else git tag "$TAG" fi HOST="${GITHUB_SERVER_URL#https://}" PUSH_URL="https://x-access-token:${GITEA_TOKEN}@${HOST}/${GITHUB_REPOSITORY}.git" git push "$PUSH_URL" "HEAD:${GITHUB_REF_NAME}" 2>/dev/null || echo "分支无新提交,跳过推送" git push "$PUSH_URL" "$TAG" 2>/dev/null || echo "tag 已在远端,跳过推送" - name: 校验版本一致性 run: | set -euo pipefail MV=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])") if [ "$MV" != "$VERSION" ]; then echo "::error::manifest.json 版本 $MV 与目标 $VERSION 不一致" exit 1 fi echo "版本一致性校验通过:v$VERSION" - name: 打包扩展 run: | set -euo pipefail ZIP="poplingo-v${VERSION}.zip" python3 - "$ZIP" <<'PY' import os, sys, zipfile zip_name = sys.argv[1] items = ['manifest.json', 'background.js', 'README.md', 'content', 'popup', 'options', 'icons'] with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as z: for item in items: if os.path.isfile(item): z.write(item, item) elif os.path.isdir(item): for root, _, files in os.walk(item): for f in files: p = os.path.join(root, f) z.write(p, p) names = zipfile.ZipFile(zip_name).namelist() assert 'manifest.json' in names, 'zip 缺少 manifest.json' print(f'打包完成: {zip_name}({len(names)} 个文件)') PY echo "ZIP=$ZIP" >> "$GITHUB_ENV" - name: 生成 Release Notes env: INPUT_NOTES: ${{ inputs.notes }} run: | set -euo pipefail if [ -n "${INPUT_NOTES:-}" ]; then NOTES="$INPUT_NOTES" echo "使用手动填写的发布说明" else PREV_TAG=$(git tag --sort=-creatordate | grep -v "^${TAG}$" | head -1 || true) if [ -n "$PREV_TAG" ]; then NOTES=$(git log --pretty=format:'- %s' "${PREV_TAG}..${TAG}") echo "changelog 范围: ${PREV_TAG}..${TAG}" else NOTES=$(git log --pretty=format:'- %s') echo "首个版本,changelog 包含全部提交" fi fi export RELEASE_NOTES="${NOTES:-初始发布}" python3 <<'PY' import json, os body = os.environ['RELEASE_NOTES'] + ( f"\n\n## 📦 安装\n\n" f"1. 下载附件 `poplingo-v{os.environ['VERSION']}.zip` 并解压\n" f"2. 打开 `chrome://extensions/`,开启「开发者模式」,「加载已解压的扩展程序」选择解压目录\n" f"3. 点击工具栏图标完成模型配置即可使用" ) meta = { 'tag_name': os.environ['TAG'], 'name': '译泡 PopLingo v' + os.environ['VERSION'], 'body': body, 'draft': False, 'prerelease': False, } with open('release_meta.json', 'w') as f: json.dump(meta, f, ensure_ascii=False) print('release_meta.json 已生成') PY - name: 创建 Release 并上传附件(幂等,可安全重试) env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | set -euo pipefail API="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}" AUTH="Authorization: token ${GITEA_TOKEN}" EXIST=$(curl -sf -H "$AUTH" "${API}/releases/tags/${TAG}" || true) if [ -n "$EXIST" ]; then ID=$(echo "$EXIST" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])') HAS_ASSET=$(echo "$EXIST" | python3 -c "import json,sys; r=json.load(sys.stdin); print(1 if any(a['name']=='$ZIP' for a in r.get('assets',[])) else 0)") echo "Release ${TAG} 已存在(id=${ID}),复用" if [ "$HAS_ASSET" = "1" ]; then echo "附件 ${ZIP} 已存在,无需重复上传" exit 0 fi else curl -sf -X POST "${API}/releases" \ -H "$AUTH" -H "Content-Type: application/json" \ --data-binary @release_meta.json > release_resp.json ID=$(python3 -c 'import json; print(json.load(open("release_resp.json"))["id"])') echo "Release 已创建(id=${ID})" fi curl -sf -X POST "${API}/releases/${ID}/assets?name=${ZIP}" \ -H "$AUTH" -F "attachment=@${ZIP}" echo echo "✅ 发布完成: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}"