Files
lunar-mini/.gitea/workflows/miniapp-preview.yml
T
gouki 4efdd20ff7
Publish Mini Program Dev Version / publish (push) Failing after 39s
fix(ci): 真正根因——Node下载截断,改用sha256硬校验+镜像源优先
根因:unofficial-builds.nodejs.org 在亚洲带宽极低,wget --timeout
只限单次读取,慢速断流仍返回成功,CI拿到截断的gzip(15MB/55MB),
解压出损坏二进制报 symbol not found(并非glibc/musl混用)。

修复:
1. sha256 硬校验拦截一切假文件(musl:8d96de02... glibc:e05a4d65...)
2. musl 版优先 npmmirror 镜像(实测与官方sha256一致),unofficial 后备
3. 循环换源重试,全部失败才报错
4. wget 改用 busybox 兼容短参数 -t/-T
2026-08-10 00:01:14 +00:00

200 lines
9.1 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.
name: Publish Mini Program Dev Version
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "mini/**"
- ".gitea/workflows/miniapp-preview.yml"
jobs:
publish:
# 标准标签:hk / bt-runner 均带 ubuntu-latest,任一空闲 runner 接单
runs-on: ubuntu-latest
steps:
# 精简 runnerAlpine)无 node,无法跑 action-checkout;直接用 git 浅克隆
- name: Checkout
env:
REPO_URL: ${{ gitea.server_url }}/${{ gitea.repository }}
REF_NAME: ${{ gitea.ref_name }}
run: |
git init -q .
git remote add origin "$REPO_URL"
git fetch -q --depth 20 origin "$REF_NAME"
git checkout -q FETCH_HEAD
git log --oneline -3
- name: Prepare Node toolchain
run: |
# 优先复用 runner 宿主机缓存的工具链(工作目录持久化)
TOOLDIR="$RUNNER_WORKSPACE/.lunar-toolchain"
mkdir -p "$TOOLDIR"
if [ -x "$TOOLDIR/node/bin/node" ] && "$TOOLDIR/node/bin/node" --version >/dev/null 2>&1; then
echo "复用缓存 Node 工具链"
else
# 清理旧缓存:Alpine busybox mv 遇已存在目录会合并而非替换,
# 残留的旧版 node 会污染环境导致 symbol not found
rm -rf "$TOOLDIR"
mkdir -p "$TOOLDIR"
# 探测 libcAlpine(musl) 用 unofficial musl 构建,其余用官方 glibc 构建(tar.gzbusybox tar 不支持 xz
# 关键教训:unofficial-builds 在亚洲带宽极低,wget --timeout 只限单次读取,
# 慢速断流仍返回成功,产生截断的 gzip(解压出损坏二进制报 symbol not found)。
# 必须用 sha256 硬校验,不匹配则换源重试。
if ldd --version 2>&1 | grep -qi musl || [ ! -e /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 ]; then
NODE_DIST="node-v22.12.0-linux-x64-musl"
NODE_SHA="8d96de0293fc3978d09e2a3fee68c66bca4efcca90f52105e6f7463636e97308"
# 优先 npmmirror(国内/亚洲快),unofficial-builds 作为后备
URLS="https://registry.npmmirror.com/-/binary/node-unofficial-builds/v22.12.0/$NODE_DIST.tar.gz
https://unofficial-builds.nodejs.org/download/release/v22.12.0/$NODE_DIST.tar.gz"
else
NODE_DIST="node-v22.12.0-linux-x64"
NODE_SHA="e05a4d65232ae2b27b3d77da2e368522fb46b923335b8e0d5f77624c32484044"
URLS="https://nodejs.org/dist/v22.12.0/$NODE_DIST.tar.gz
https://mirrors.aliyun.com/nodejs-release/v22.12.0/$NODE_DIST.tar.gz"
fi
DL_OK=""
for url in $URLS; do
echo "尝试下载: $url"
if wget -t 2 -T 120 -q -O "$TOOLDIR/node.tgz" "$url"; then
# sha256 硬校验(busybox 有 sha256sum):拦截截断/错误页等假文件
if [ "$(sha256sum "$TOOLDIR/node.tgz" | cut -d' ' -f1)" = "$NODE_SHA" ]; then
echo "下载成功且校验通过"
DL_OK=1
break
fi
echo "sha256 校验失败,文件可能截断,尝试下一源"
else
echo "wget 下载失败,尝试下一源"
fi
rm -f "$TOOLDIR/node.tgz"
done
test -n "$DL_OK" || { echo "::error::所有下载源均失败或校验不通过"; exit 1; }
tar -C "$TOOLDIR" -xzf "$TOOLDIR/node.tgz"
mv "$TOOLDIR/$NODE_DIST" "$TOOLDIR/node"
rm -f "$TOOLDIR/node.tgz"
fi
# 验证二进制可执行,失败则清缓存并报错(避免坏缓存被后续 run 复用)
if ! "$TOOLDIR/node/bin/node" --version; then
echo "::error::Node 二进制无法执行,请检查下载源与 libc 匹配"
rm -rf "$TOOLDIR"
exit 1
fi
echo "PATH=$TOOLDIR/node/bin:$PATH" >> "$GITEA_ENV"
- name: Verify Node runtime
run: node --version
- name: Install dependencies
working-directory: mini
run: npm ci
- name: Run tests
working-directory: mini
run: npm test
- name: Configure production API and app version
working-directory: mini
env:
MINIAPP_BUILD_NUMBER: ${{ gitea.run_number }}
GITEA_SHA: ${{ gitea.sha }}
run: |
test -n "$MINIAPP_BUILD_NUMBER"
VERSION="$(node -e 'const {resolveVersion}=require("./ci/resolve-version.cjs"); process.stdout.write(resolveVersion(process.cwd()))')"
test -n "$VERSION"
mkdir -p ci-artifacts
printf '%s\n' "$VERSION" > ci-artifacts/resolved-version.txt
echo "Resolved Mini Program version for build+upload: $VERSION"
# 注入版本号到 utils/version.js
BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
COMMIT_SHORT="${GITEA_SHA:0:7}"
cat > utils/version.js <<EOF
/**
* 版本号配置文件
* 此文件由 CI 构建时自动生成,请勿手动修改
*/
module.exports = {
version: "$VERSION",
buildNumber: "$MINIAPP_BUILD_NUMBER",
buildTime: "$BUILD_TIME",
commitSha: "$COMMIT_SHORT",
};
EOF
echo "Version info injected into utils/version.js:"
cat utils/version.js
- name: Materialize the upload key
env:
WECHAT_CI_PRIVATE_KEY: ${{ secrets.WECHAT_CI_PRIVATE_KEY }}
run: |
test -n "$WECHAT_CI_PRIVATE_KEY"
umask 077
printf '%s' "$WECHAT_CI_PRIVATE_KEY" > "$RUNNER_TEMP/wechat-ci.key"
- name: Upload WeChat development version
working-directory: mini
env:
MINIAPP_APPID: ${{ vars.MINIAPP_APPID }}
MINIAPP_ROBOT: ${{ vars.MINIAPP_ROBOT }}
# Prefer the version resolved before build so UI label matches WeChat upload.
MINIAPP_BUILD_NUMBER: ${{ gitea.run_number }}
WECHAT_CI_PRIVATE_KEY_PATH: ${{ runner.temp }}/wechat-ci.key
MINIAPP_UPLOAD_RESULT: ${{ gitea.workspace }}/mini/ci-artifacts/upload-result.json
MINIAPP_VERSION_LABEL: ${{ gitea.sha }}
MINIAPP_UPLOAD_DESC_ITEMS: "3"
MINIAPP_UPLOAD_DESC_MAX: "100"
run: |
test -n "$MINIAPP_APPID"
test -n "$MINIAPP_BUILD_NUMBER"
test -s ci-artifacts/resolved-version.txt
RESOLVED_VERSION="$(tr -d '\n' < ci-artifacts/resolved-version.txt)"
# Force upload to the same version that was baked into the JS bundle.
export MINIAPP_VERSION_OVERRIDE="$RESOLVED_VERSION"
mkdir -p ci-artifacts
node -e 'const c=require("./project.config.json"); if(c.appid!==process.env.MINIAPP_APPID){console.error("MINIAPP_APPID mismatch with project.config.json", process.env.MINIAPP_APPID, c.appid); process.exit(1)}'
node -e 'const {resolveVersion}=require("./ci/resolve-version.cjs"); console.log("Resolved Mini Program version:", resolveVersion(process.cwd()))'
node -e 'const {resolveUploadDesc}=require("./ci/resolve-upload-desc.cjs"); console.log("Resolved upload remark:", resolveUploadDesc({repoRoot:require("path").resolve(".."), versionLabel:process.env.MINIAPP_VERSION_LABEL}))'
# Normalize PEM newlines that may be flattened when stored in Secrets.
# 用 node 处理(部分 runner 无 python3
node - <<'JS'
const fs = require("fs");
const p = process.env.WECHAT_CI_PRIVATE_KEY_PATH;
let t = fs.readFileSync(p, "utf8");
if (t.includes("\\n") && t.includes("BEGIN")) t = t.split("\\n").join("\n");
t = t.split("\r\n").join("\n").trim() + "\n";
fs.writeFileSync(p, t);
if (!/PRIVATE KEY/.test(t)) { console.error("WECHAT_CI_PRIVATE_KEY is not a PEM private key"); process.exit(1); }
console.log("private_key_lines=", t.split("\n").length);
JS
node ci/upload-ci.cjs
test -s ci-artifacts/upload-result.json
cat ci-artifacts/upload-result.json
- name: Send success notification
if: success()
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
VERSION="$(tr -d '\n' < mini/ci-artifacts/resolved-version.txt)"
bash .gitea/scripts/notify.sh success "小程序开发版上传成功" "版本: ${VERSION}" || echo "通知发送失败,不影响主流程"
- name: Send failure notification
if: failure()
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
bash .gitea/scripts/notify.sh failure \
"小程序开发版上传失败" \
"请检查 Actions 日志了解失败原因" || echo "通知发送失败"
- name: Remove temporary credentials
if: always()
run: rm -f "$RUNNER_TEMP/wechat-ci.key"