Files
HomeAgent/.github/workflows/release.yml
JianFeeeee dbb787a658 refactor(ci,docs): gitcode 镜像仓名收为单一定义点,取值 HomeAgent(实测 200)
gitcode 镜像目标仓名此前在 3 个文件里各写死多份字面量,改名时要遍改且极
易漏(漏一处 = 一个静默死链,而且失败面在 release 里只有一格)。本批只做
「收成单点」,不改任何取值、不碰代码。

三个定义点(每个文件恰好 1 处,消费点全部走变量):
1. .github/workflows/ci.yml → job `sync-gitcode-main` 的 env.GC_REPO:232
   消费点 2 处命令行:git ls-remote 探测、git push main。
2. .github/workflows/release.yml → job `sync-gitcode` 的 env.GC_REPO:375
   消费点 4 处命令行:git ls-remote 探测、push tag、POST /releases、
   upload_assets.py 的 GITCODE_REPO 传入。
   (两个 workflow 是独立文件,不能共享 env,故各自一处定义。)
3. deploy/scripts/upload_assets.py → os.environ.get("GITCODE_REPO", ...) 默认值。
   手工跑脚本时的兜底,与两个 workflow 同值。

为什么取值 JianFeeeee/HomeAgent 而不是 HologramAgent:
GitHub 主仓已改名 HologramAgent,但 **gitcode 侧镜像仓没有跟着改名**。
curl 直连实测(2026-10-21):
  https://gitcode.com/JianFeeeee/HomeAgent      → 200   ← 当前取值
  https://gitcode.com/JianFeeeee/HologramAgent  → 404
指向 HologramAgent 会让每次发版/推 main 都在镜像同步这步红掉,且国内镜像
停止更新。

两个 job 的失败面不同(容易搞混,此处写明):
- release.yml 的 sync-gitcode:needs [prepare, publish]。探测 exit 1 ⇒
  **只红「镜像同步」这一格**;GitHub 侧 release 产物已经发出去了。
- ci.yml 的 sync-gitcode-main:needs [go, race, cross, gui, csrc, docs] 全绿
  后才跑。探测 exit 1 ⇒ **main 的整条 CI 红**(不是一格)。
所以仓名写错的真实后果是「必红一格 + 国内镜像不更新」,不是「发不出包」。

何时切回 HologramAgent:等 gitcode 上把镜像仓真正改名/新建成功、
https://gitcode.com/JianFeeeee/HologramAgent 返回 200 之后,
**只改上面这三行**即可,两个 workflow 的消费点与 runbook 均无需再动。

docs/zh/ci-cd-runbook.md 同步三节:单点化表格(定义处/消费点)、当前取值与
200/404 实测依据、两个 job 的失败面差异;并把 §3.4 的 curl 验活地址与
upload_assets 手工调用示例同步到 HomeAgent(原写法是已 404 的死链)。

判据(含变异,全部实跑):
- 两个 workflow 可被 yaml.safe_load 解析;每个 jobs[..].env.GC_REPO 恰好 1 处
- 每个 step 的 run 块抽出来喂 bash -n,全部无语法错
- 可执行形态里的 gitcode 目标仓名不含 HologramAgent(注释行豁免)
- 变异:把任一 GC_REPO 定义改回 HologramAgent、或把某消费点还原成字面量,
  判据均报红
- 零代码改动:git status 无 .go/.lua/go.mod/go.sum;go build ./... 通过
2026-10-21 19:55:59 +08:00

465 lines
21 KiB
YAML
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.

# 发布流水线:release/** 分支推送即发版。
#
# 设计依据 docs/git-branching.md §七(发版产物清单)与 git-release-discipline
# skill。核心事实:**推 tag ≠ 完成发版** —— 完整发版是四件事:
# bump meta.Version → 打 tag → 打包产物 → 建 release 条目并上传附件。
# (v1.3.1–v1.3.6 曾只推了 tag,产物与 release 条目全缺,事后补做。)
#
# 版本号来源:internal/meta/meta.go 的 Version(唯一事实源)。
# 所以发版动作 = 在 release/vX.Y.x 上把 meta.Version 改成目标版本后推送。
# 版本未变的推送(如改文档)会因 tag 已存在而**整轮跳过**,不会重复发版。
#
# 发版前的 go test 门可以显式跳过(见下面 skip_tests 的说明)。
name: Release
on:
push:
branches: ['release/**']
workflow_dispatch:
inputs:
skip_tests:
description: '跳过发版前的 go test 门(仅用于已知红的历史维护线)'
type: boolean
default: false
# 发布必须能写仓库(打 tag、建 release、传附件)。
permissions:
contents: write
# 发布不允许并发/取消:半途中断会留下 tag 存在但附件不全的状态。
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
# gojieba 需要 cgo;onnxruntime 版本经 dlopen 加载,编译期无需装 ORT。
CGO_ENABLED: 1
GOFLAGS: -buildvcs=false
# CI 用的大资产(模型/运行库)存于这个 release。
ASSETS_TAG: ci-assets-v1
jobs:
# ── 读版本号并判断是否需要发版 ──
prepare:
name: Prepare
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
version: ${{ steps.ver.outputs.version }}
tag: ${{ steps.ver.outputs.tag }}
prerelease: ${{ steps.ver.outputs.prerelease }}
exists: ${{ steps.ver.outputs.exists }}
skip_tests: ${{ steps.ver.outputs.skip_tests }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
# 发版前的 go test 门为什么可以跳过:
#
# 新旧发布线的测试健康状况不同。实测 release/v1.3.x(历史维护线)上
# internal/plugins 的 TestRealPlugin_DeepSearchKeepsSharedBackendOnStop
# 失败、GUI 尚无 npm test 脚本、csrc 基础设施不存在 —— 而这三项在 main
# 上都正常。给旧线补新流水线等于用今天的门去量旧代码,硬门会让该线
# **完全无法发版**。
#
# 故:默认严格(测试必跑);发版人若确知该线测试是既存红的,可在
# 发版 commit 里写 [skip-release-tests] 显式跳过 —— 决定因此记录在
# **定义该次发版的那个 commit** 里,git 历史可审计。
# go build 仍是硬门(产物不可能建立在编译失败的代码上)。
- id: ver
name: 读取 meta.Version 并检查 tag
run: |
set -euo pipefail
V=$(sed -n 's/^[[:space:]]*Version = "\(.*\)"/\1/p' \
internal/meta/meta.go | head -1)
if [ -z "$V" ]; then
echo "ERROR: 无法从 internal/meta/meta.go 读出 Version"
exit 1
fi
echo "version=$V" >> "$GITHUB_OUTPUT"
echo "tag=v$V" >> "$GITHUB_OUTPUT"
# SemVer 预发布(1.3.13-beta.1)⇒ release 标记为预发布
case "$V" in
*-*) echo "prerelease=true" >> "$GITHUB_OUTPUT" ;;
*) echo "prerelease=false" >> "$GITHUB_OUTPUT" ;;
esac
# 幂等闸门:tag 已存在说明该版本发过了,整轮跳过。
if git ls-remote --exit-code --tags origin "refs/tags/v$V" \
>/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo " tag v$V 已存在 —— 跳过发版"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo " 将为 v$V 发版"
fi
# 是否跳过发版前的 go test 门(默认不跳)。
# 两个来源:手动触发的输入,或发版 commit 里的显式标记。
# 后者使决定落在定义该次发版的 commit 上,可以从 git 历史审计。
#
# 标记查在**改动 meta.Version 的那个提交**上,而不是 HEAD:
# 发版提交之后往往还会跟几个提交(如同步 workflow、改文档),
# 若只看 HEAD,标记就会被后续提交顶掉,静默失效。
SKIP="${{ inputs.skip_tests }}"
MARKER=0
REL_COMMIT=$(git log -1 --format=%H -- internal/meta/meta.go)
REL_MSG=$(git log -1 --pretty=%B "$REL_COMMIT")
case "$REL_MSG" in
*'[skip-release-tests]'*) MARKER=1 ;;
*) MARKER=0 ;;
esac
echo " 发版提交: ${REL_COMMIT:0:12}"
if [ "$SKIP" = "true" ] || [ "$MARKER" = "1" ]; then
echo "skip_tests=true" >> "$GITHUB_OUTPUT"
echo ""
echo " ⚠️ **已请求跳过发版前的 go test 门**"
echo " 来源:${SKIP} = true / commit 标记 = $MARKER"
echo " 后果:产物可能建立在单元测试失败的代码上。"
echo " 理由应当记录在发版 commit 的正文里。"
else
echo "skip_tests=false" >> "$GITHUB_OUTPUT"
echo " 发版前会跑 go test 门(可用 [skip-release-tests] 标记跳过)"
fi
# ── 构建 Linux 产物(amd64)──
#
# 三个 deb + 一个 tar.gz,总约 2.4GB(server/full/tar 含 719MB 模型)。
# 编译不需要 ONNX Runtime —— onnxruntime_go 是 dlopen 方式,运行期才加载
# libonnxruntime.so;但**打包**需要它(要打进 deb),故从 ASSETS_TAG 下载。
build-linux:
name: Build linux/amd64
needs: prepare
if: needs.prepare.outputs.exists == 'false'
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: 确认 cgo 工具链
run: |
gcc --version | head -1
g++ --version | head -1
# 发版前的门。
#
# go build 是**硬门**:产物不可能建立在编译失败的代码上。
# go test 默认也跑,但可在发版 commit 里写 [skip-release-tests] 跳过
# —— 历史维护线的既有红测试不应阻断该线的一切发版(详见 prepare job)。
- name: go build(硬门)
run: |
set -euo pipefail
go build ./...
- name: go test(发版前验证)
if: needs.prepare.outputs.skip_tests != 'true'
run: go test ./... -count=1 -timeout 20m
- name: go test 被跳过(显式声明的后果)
if: needs.prepare.outputs.skip_tests == 'true'
run: |
echo "::warning title=go test 门已跳过::本次发版未跑 go test,产物可能建立在单元测试失败的代码上。"
# GUI 依赖 Electron 运行时。打包脚本从两处找它:
# 1) ~/.cache/electron 里的 electron-v<ver>-linux-<arch>.zip
# 2) cmd/gui/node_modules/electron/dist(同架构时)
# 全新 runner 两处都没有 —— 而脚本在都没有时**只能跳过 GUI**,
# 于是 client/full 包会静默地不含界面(这正是脚本作者担心的“假包”)。
# 所以这里显式装一份:npm 会解析出 ^33.0.0 的实际版本并落到
# node_modules,脚本便走第 2 条路径(runner 是 amd64 == 目标架构)。
#
# ⚠️ 不能用 `npm install --production`(那会跳过 devDependencies,
# 而 electron 正是 devDependency)。
- uses: actions/setup-node@v7
with:
node-version: '22'
- name: 安装 Electron(GUI 打包需要)
working-directory: cmd/gui
run: |
set -euo pipefail
# 用 npm ci 而非 `npm install electron@<range>`:后者是非确定性的
# (range 会随上游发布漂到新版本),且锁不住传递依赖。
# package-lock.json 里已锁定 electron(lockfileVersion 3),
# ci 严格按 lock 安装,同一个 commit 永远得到同一套依赖。
npm ci --no-audit --no-fund
test -f node_modules/electron/dist/electron
echo " 已就绪:$(node_modules/electron/dist/electron --version)"
- name: 下载构建资产(模型 + ONNX Runtime)
run: |
set -euo pipefail
BASE="https://github.com/${GITHUB_REPOSITORY}/releases/download/${ASSETS_TAG}"
mkdir -p /tmp/assets/model /tmp/assets/ort
for f in chinese-clip-vit-b16-onnx.tar \
onnxruntime-linux-amd64-1.28.0.tar SHA256SUMS; do
echo " 下载 $f"
curl -sSL --retry 3 -o "/tmp/assets/$f" "$BASE/$f"
done
# 校验(资产是构建输入,损坏会打出坏包)
(cd /tmp/assets && sha256sum -c SHA256SUMS)
tar -xf /tmp/assets/chinese-clip-vit-b16-onnx.tar \
-C /tmp/assets/model
ORT_TAR=/tmp/assets/onnxruntime-linux-amd64-1.28.0.tar
tar -xf "$ORT_TAR" -C /tmp/assets/ort
echo " 模型文件:"
ls /tmp/assets/model/chinese-clip-vit-b16-onnx
echo " ORT 文件:"
ls /tmp/assets/ort
- name: 打包(tar.gz + full/server/client deb)
env:
VERSION: ${{ needs.prepare.outputs.version }}
CHINESECLIP_BUNDLE_DIR: /tmp/assets/model/chinese-clip-vit-b16-onnx
ONNXRUNTIME_ASSET_DIR: /tmp/assets/ort
run: |
set -euo pipefail
bash deploy/packaging/package-linux.sh amd64 all
- name: 平铺产物(附件必须同目录,SHA256SUMS 用平铺名)
run: |
set -euo pipefail
mkdir -p /tmp/out
cp dist/linux/deb/*.deb /tmp/out/
cp dist/linux/tar/*.tar.gz /tmp/out/
cp dist/linux/SHA256SUMS /tmp/out/
echo " 产物:"
for f in /tmp/out/*; do
printf " %8.1fMB %s\n" \
"$(stat -c %s "$f" | awk '{print $1/1048576}')" "$(basename "$f")"
done
- name: 验证产物(deb 元数据 + 校验和自验)
run: |
set -euo pipefail
cd /tmp/out
for f in *.deb; do
echo " $f"
dpkg-deb -f "$f" Package Version Architecture | sed 's/^/ /'
done
# full/server 必须真的带模型,否则是“默认启用但装完不能用”的假包。
#
# ★ 不能用 grep -q:它匹配到就退出,关闭管道读端,dpkg-deb 内部
# 的 tar 写 stdout 时收到 EPIPE(“stdout: write error”),
# 在 pipefail 下整条 pipeline 变成失败 —— 检测项本身是好的,
# 却被检测手段误杀(首次试发布就死在这里)。改用 >/dev/null,
# grep 会读完整个输入再退出,不产生 SIGPIPE。
dpkg-deb -c hologramagent-full_*_amd64.deb \
| grep "chinese-clip-vit-b16-onnx/TextEncoder.onnx" >/dev/null
echo " ✓ full 包含模型"
dpkg-deb -c hologramagent-full_*_amd64.deb \
| grep "libonnxruntime.so" >/dev/null
echo " ✓ full 包含 ONNX Runtime"
dpkg-deb -c hologramagent-server_*_amd64.deb \
| grep "chinese-clip-vit-b16-onnx/TextEncoder.onnx" >/dev/null
echo " ✓ server 包含模型"
sha256sum -c SHA256SUMS
- uses: actions/upload-artifact@v7
with:
name: linux-amd64
path: /tmp/out/*
retention-days: 7
if-no-files-found: error
# ── 建 tag、建 release、上传附件 ──
publish:
name: Publish
needs: [prepare, build-linux]
if: needs.prepare.outputs.exists == 'false'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/download-artifact@v8
with:
name: linux-amd64
path: dist
- name: 打 tag(打在触发本次发版的 commit 上)
env:
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "$TAG"
git push origin "$TAG"
- name: 建 release 并上传附件
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
VERSION: ${{ needs.prepare.outputs.version }}
PRE: ${{ needs.prepare.outputs.prerelease }}
run: |
set -euo pipefail
cd dist
FLAGS=()
[ "$PRE" = "true" ] && FLAGS+=(--prerelease)
gh release create "$TAG" \
--title "$TAG" \
--notes "HomeAgent $VERSION
产物清单与校验见 SHA256SUMS。
- \`hologramagent_${VERSION}_linux_amd64.tar.gz\` — 内核 + CLI + GUI 打包
- \`hologramagent-client_${VERSION}_amd64.deb\` — 客户端
- \`hologramagent-server_${VERSION}_amd64.deb\` — 服务端(含向量模型)
- \`hologramagent-full_${VERSION}_amd64.deb\` — 全量" \
"${FLAGS[@]}" \
./*.deb ./*.tar.gz ./SHA256SUMS
echo "=== release 内容 ==="
gh release view "$TAG" --json assets \
--jq '.assets[] | " \(.name) \(.size) 字节"'
- name: 回读校验(下载回来验证附件可读且校验和成立)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
mkdir -p /tmp/back
cd /tmp/back
# ★ 必须显式 --repo:gh 默认从**当前目录的 git 上下文**推断仓库,
# 而 /tmp/back 不是 git 仓库 ⇒ 报
# "failed to run git: fatal: not a git repository"。
# 首次试发布就死在这里 —— 产物其实全部上传成功(tag 与
# release 已建、2.40GB 附件齐备),只是这道回读校验自己失败了。
gh release download "$TAG" --repo "$GITHUB_REPOSITORY"
for f in *; do
printf " %8.1fMB %s\n" \
"$(stat -c %s "$f" | awk '{print $1/1048576}')" "$f"
done
sha256sum -c SHA256SUMS
echo " ✓ 回读校验通过"
# ── 同步到 gitcode(国内镜像)──
#
# 需要仓库 secret GITCODE_TOKEN;未配置则跳过(不阻断 GitHub 侧发布)。
# gitcode 的 release 附件是"同名只写一次",故只在此处上传一次。
sync-gitcode:
name: Sync to gitcode
needs: [prepare, publish]
if: needs.prepare.outputs.exists == 'false'
runs-on: ubuntu-latest
timeout-minutes: 60
env:
# ══════════════════════════════════════════════════════════════════
# ★★ gitcode 镜像目标仓名 —— **本 job 的唯一定义处**。
# 改 gitcode 仓名时只改这一行;下面所有消费点都走 ${GC_REPO}。
#
# 取值依据(实测 2026-10-21,curl 直连,非推测):
# https://gitcode.com/JianFeeeee/HomeAgent → 200 ← 当前用这个
# https://gitcode.com/JianFeeeee/HologramAgent → 404
# https://gitcode.com/JianFeeeee/homeagent-sdk → 200
# https://gitcode.com/JianFeeeee/hologramagentsdk → 200
# https://gitcode.com/JianFeeeee/homeagentsdk → 404
#
# 背景:GitHub 主仓已由 HomeAgent 改名为 HologramAgent,但 **gitcode 侧
# 的镜像仓没有跟着改名**,gitcode 上 HologramAgent 仍是 404。因此这里
# 必须指向仍然存在的 HomeAgent,否则每次发版都会红在镜像同步这一格。
#
# 何时改回 HologramAgent:在 gitcode 上把镜像仓真正改名/新建成功、
# 且 https://gitcode.com/JianFeeeee/HologramAgent 返回 200 之后,
# **只改这一行**即可,无需再动其他文件。
# ══════════════════════════════════════════════════════════════════
GC_REPO: JianFeeeee/HomeAgent
steps:
- uses: actions/checkout@v7
- id: tok
name: 检查 gitcode 凭据
run: |
if [ -n "${{ secrets.GITCODE_TOKEN }}" ]; then
echo "ok=true" >> "$GITHUB_OUTPUT"
else
echo "ok=false" >> "$GITHUB_OUTPUT"
echo " 未配置 GITCODE_TOKEN —— 跳过 gitcode 同步"
fi
- uses: actions/download-artifact@v8
if: steps.tok.outputs.ok == 'true'
with:
name: linux-amd64
path: dist
# ★ 前置检查:gitcode 镜像仓是否存在。
# 改名(HomeAgent → HologramAgent)后 gitcode 目标仓需要**人工新建**,
# 不探的话失败会埋在 push/curl 的原始输出里,看不出是「仓没建」。
# 语义:token 未配 = 可选项跳过(上一步);token 有但仓不存在 = 硬失败。
- name: 前置检查 gitcode 目标仓是否存在
if: steps.tok.outputs.ok == 'true'
env:
GC_TOKEN: ${{ secrets.GITCODE_TOKEN }}
run: |
set -euo pipefail
GC_URL="https://JianFeeeee:${GC_TOKEN}@gitcode.com"
# GC_REPO 来自 job 级 env(唯一定义处),此处不重复字面量。
if out=$(GIT_TERMINAL_PROMPT=0 git ls-remote --heads \
"${GC_URL}/${GC_REPO}.git" 2>&1); then
echo " 目标仓可访问:gitcode.com/${GC_REPO}"
else
# 日志脱敏:git 报错可能把 URL 的 userinfo(token 就在里面)带出来。
# 先按 URL 结构遮 https://<userinfo>@ —— 与 token 内容无关,稳;
# 再尽力遮一次字面量(token 含特殊字符时可能不生效,故只做兜底)。
err=$(printf '%s\n' "$out" | sed -E 's#(https?://)[^@/[:space:]]*@#\1***@#g')
err=$(printf '%s\n' "$err" | sed "s|${GC_TOKEN}|***|g" 2>/dev/null || printf '%s\n' "$err")
case "$err" in
# gitcode 真实报错(2026-10 实测):目标仓不存在时返回
# remote: <CH.00905403> The project you were looking for could not be found.
# fatal: ... The requested URL returned error: 403
# 注意是 "could not be found" 与 403,不是 github 那种 "Repository not found"/404。
*"could not be found"*|*"not found"*|*"Not Found"*|*"404"*|*"does not exist"*|*"does not appear to be a git repository"*)
echo " ✗ gitcode 目标仓不存在:gitcode.com/${GC_REPO}"
echo " 请先在该账号下创建镜像仓(只读镜像),再重跑本 job:"
echo " https://gitcode.com/${GC_REPO}"
;;
*"403"*|*"401"*|*"Forbidden"*|*"Unauthorized"*)
echo " ✗ 无法访问 gitcode 目标仓 gitcode.com/${GC_REPO}:仓不存在,或 token 无权访问"
echo " 若仓已建,请确认 GITCODE_TOKEN 属于该仓的推送账号:"
echo " https://gitcode.com/${GC_REPO}"
;;
*)
echo " ✗ 无法访问 gitcode 目标仓 gitcode.com/${GC_REPO}(网络或凭据问题):"
printf '%s\n' "$err" | sed 's/^/ /'
;;
esac
# 显式失败 —— 不静默跳过,也不 echo 一句就当成功。
exit 1
fi
- name: 推 tag 与附件到 gitcode
if: steps.tok.outputs.ok == 'true'
env:
GC_TOKEN: ${{ secrets.GITCODE_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
# GC_REPO 来自 job 级 env(唯一定义处),此处不重复字面量。
GC_URL="https://JianFeeeee:${GC_TOKEN}@gitcode.com"
# 1) 推 tag(附件上传前 release 条目必须先存在)
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "$TAG" 2>/dev/null || true
git push "${GC_URL}/${GC_REPO}.git" "$TAG"
# 2) 建 release 条目
curl -sS --max-time 60 -X POST \
-H "private-token: ${GC_TOKEN}" \
-H "Content-Type: application/json" \
"https://gitcode.com/api/v5/repos/${GC_REPO}/releases" \
-d "{\"tag_name\":\"$TAG\",\"body\":\"同步自 GitHub\"}" \
-o /tmp/.gcrel -w " 建 release → %{http_code}\n"
# 3) 上传附件(用仓库既有脚本,它处理 OBS 预签名两步流程)
cd dist
# 脚本的路径语义是 os.path.join(ASSET_DIR, name)。
# 不传文件名时它扫描 ASSET_DIR 并按后缀识别发布产物 —— 这里正合适
# (.deb/.tar.gz/SHA256SUMS 都在白名单里)。
# 不要传 "./x" 或 "dist/x":那会拼成 dist/dist/x。
ASSET_DIR=. GITCODE_REPO="${GC_REPO}" \
python3 ../deploy/scripts/upload_assets.py "$TAG" "$GC_TOKEN"