Build Dist Tarball #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Dist Tarball | |
| # 方案 B:CI 只 build 一次(dist 跨平台),ship dist+lockfile(不 ship node_modules)+ pnpm.cjs | |
| # + 4 平台 portable Node → 4 个瘦 tarball。 | |
| # 用户侧 install.sh 跑 `pnpm install --prod --frozen-lockfile`(用 bundled pnpm + portable Node) | |
| # 拉依赖+native prebuilt,无 tsc/无 OOM。 | |
| on: | |
| push: | |
| tags: ['v*'] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Release tag to build + attach (e.g. v5.6.0);由 release.yml 推 tag 后触发' | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: build-dist-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout xiaobei | |
| uses: actions/checkout@v5 | |
| - name: Read pinned openclaw version | |
| id: pin | |
| run: | | |
| source openclaw.version | |
| echo "commit=$OPENCLAW_COMMIT" >> "$GITHUB_OUTPUT" | |
| echo "version=$OPENCLAW_VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Setup Node + pnpm (build env) | |
| # v6:package-manager-cache 限 npm-only,避免 pnpm 装好前自动开缓存报 | |
| # "Unable to locate executable file: pnpm"(setup-node#1351) | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.15.0' | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10.30.2 | |
| - name: Clone openclaw at pinned commit | |
| run: | | |
| git init openclaw | |
| git -C openclaw remote add origin https://github.com/openclaw/openclaw.git | |
| git -C openclaw fetch --depth=1 origin ${{ steps.pin.outputs.commit }} | |
| git -C openclaw checkout FETCH_HEAD | |
| - name: Apply patches + pnpm install (for build) | |
| run: bash scripts/apply-addons.sh --no-build --no-restart --skip-crew | |
| - name: Build openclaw dist | |
| run: | | |
| cd openclaw | |
| NODE_OPTIONS=--max-old-space-size=8192 pnpm build | |
| NODE_OPTIONS=--max-old-space-size=8192 pnpm ui:build | |
| env: | |
| CI: 'true' | |
| - name: Build camoufox-cli fork dist (no global install) | |
| run: | | |
| cd patches/camoufox-cli | |
| npm install --registry=https://registry.npmmirror.com | |
| npm run build | |
| - name: Fetch standalone pnpm (npm package, self-contained) | |
| run: | | |
| curl -fsSL -o /tmp/pnpm.tgz https://registry.npmmirror.com/pnpm/-/pnpm-11.2.2.tgz | |
| rm -rf /tmp/pnpm-pkg | |
| mkdir -p /tmp/pnpm-pkg | |
| tar -xzf /tmp/pnpm.tgz -C /tmp/pnpm-pkg | |
| # 入口 bin/pnpm.mjs(main 字段),dist/pnpm.mjs 是自包含 bundle | |
| test -f /tmp/pnpm-pkg/package/bin/pnpm.mjs | |
| test -f /tmp/pnpm-pkg/package/dist/pnpm.mjs | |
| - name: Stage engine (no node_modules) | |
| id: stage | |
| run: | | |
| set -euo pipefail | |
| REPO="$GITHUB_WORKSPACE" | |
| STAGE="$RUNNER_TEMP/engine" | |
| rm -rf "$STAGE" | |
| mkdir -p "$STAGE/bin" "$STAGE/tools" | |
| # 1. openclaw 引擎:dist + 源码 + lockfile + package.jsons,排除 node_modules / .git / 缓存 | |
| # 用户侧 pnpm install --prod 按 lockfile 重建 node_modules | |
| rsync -a \ | |
| --exclude='node_modules' \ | |
| --exclude='.git' \ | |
| --exclude='*.tsbuildinfo' \ | |
| --exclude='coverage' \ | |
| "$REPO/openclaw/" "$STAGE/openclaw/" | |
| # 2. standalone pnpm(用户侧 pnpm install --prod 用,入口 tools/pnpm/bin/pnpm.mjs) | |
| cp -a /tmp/pnpm-pkg/package "$STAGE/tools/pnpm" | |
| # 3. camoufox-cli fork(dist 已 build,用户侧 npm install -g .) | |
| rsync -a --exclude='node_modules' --exclude='.git' \ | |
| "$REPO/patches/camoufox-cli/" "$STAGE/camoufox-cli/" | |
| # 4. xiaobei 模板与脚本 | |
| cp -a "$REPO/crews" "$STAGE/crews" | |
| cp -a "$REPO/skills" "$STAGE/skills" | |
| cp -a "$REPO/config-templates" "$STAGE/config-templates" | |
| cp -a "$REPO/scripts" "$STAGE/scripts" | |
| # awada 本地插件(TS,jiti 加载;ship 源码 + lockfile,排除 node_modules, | |
| # 用户侧 install.sh 在 awada/ 下 npm install --omit=dev 装 ws+zod) | |
| rsync -a --exclude='node_modules' --exclude='.git' \ | |
| "$REPO/awada/" "$STAGE/awada/" | |
| cp "$REPO/openclaw.version" "$REPO/version" "$STAGE/" | |
| # 仓根 requirements.txt(install.sh install_python_deps 扫 skills/crews/仓根, | |
| # 不打进则用户侧 Step 4 报 "No requirements.txt found") | |
| [ -f "$REPO/requirements.txt" ] && cp "$REPO/requirements.txt" "$STAGE/" || true | |
| # openclaw-weixin 插件 pin(install.sh 据此装插件,国内 npmmirror) | |
| [ -f "$REPO/openclaw-weixin.version.json" ] && cp "$REPO/openclaw-weixin.version.json" "$STAGE/" || true | |
| # 5. 体积 breakdown(诊断) | |
| ( | |
| set +e +o pipefail | |
| echo "📊 engine top-level:" | |
| du -sh "$STAGE"/* 2>/dev/null | sort -rh | |
| echo "📊 openclaw top-level:" | |
| du -sh "$STAGE/openclaw"/* 2>/dev/null | sort -rh | head -15 | |
| ) || true | |
| echo "stage_dir=$STAGE" >> "$GITHUB_OUTPUT" | |
| - name: Build 4 platform tarballs (engine + portable Node) | |
| id: buildtars | |
| run: | | |
| set -euo pipefail | |
| STAGE="${{ steps.stage.outputs.stage_dir }}" | |
| XIAOBEI_VER="$(tr -d '[:space:]' < "$GITHUB_WORKSPACE/version")" | |
| # asset tag 优先级:workflow_dispatch inputs.tag > git tag(push 触发)> version 文件 | |
| INPUT_TAG="${{ inputs.tag }}" | |
| REF_NAME="${GITHUB_REF##*/}" | |
| if [[ -n "$INPUT_TAG" ]]; then ASSET_TAG="$INPUT_TAG" | |
| elif [[ "$REF_NAME" == v* ]]; then ASSET_TAG="$REF_NAME" | |
| else ASSET_TAG="$XIAOBEI_VER"; fi | |
| echo "ASSET_TAG=$ASSET_TAG" | |
| mkdir -p "$RUNNER_TEMP/out" | |
| # 平台 → (node_url, subdir) | |
| declare -A PLATFORMS=( | |
| [linux-x64]="https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.xz|node-v24.15.0-linux-x64" | |
| [mac-arm64]="https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.xz|node-v24.15.0-darwin-arm64" | |
| [mac-x64]="https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.xz|node-v24.15.0-darwin-x64" | |
| [win-x64]="https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip|node-v24.15.0-win-x64" | |
| ) | |
| for plat in linux-x64 mac-arm64 mac-x64 win-x64; do | |
| IFS='|' read -r url subdir <<< "${PLATFORMS[$plat]}" | |
| echo "=== packaging $plat ===" | |
| PKG="$RUNNER_TEMP/pkg-$plat" | |
| rm -rf "$PKG" | |
| cp -a "$STAGE" "$PKG" | |
| # asset 名用 git tag(release 的 source of truth),与 install.sh 按 tag 拉对齐 | |
| TAG="$ASSET_TAG" | |
| # portable Node | |
| if [[ "$plat" == win-x64 ]]; then | |
| curl -fsSL -o /tmp/node.zip "$url" | |
| unzip -q /tmp/node.zip -d /tmp/ | |
| mv "/tmp/$subdir" "$PKG/tools/node" | |
| else | |
| curl -fsSL -o /tmp/node.tar.xz "$url" | |
| tar -xJf /tmp/node.tar.xz -C /tmp/ | |
| mv "/tmp/$subdir" "$PKG/tools/node" | |
| fi | |
| # wrapper: <ROOT>/bin/openclaw → portable node + openclaw.mjs(ROOT 默认 ~/.xiaobei) | |
| cat > "$PKG/bin/openclaw" <<'EOF' | |
| #!/usr/bin/env bash | |
| HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | |
| exec "$HERE/tools/node/bin/node" "$HERE/openclaw/openclaw.mjs" "$@" | |
| EOF | |
| chmod +x "$PKG/bin/openclaw" | |
| # Windows 原生 .cmd wrapper(WSL/Git Bash 用户可用上面 bash 版) | |
| if [[ "$plat" == win-x64 ]]; then | |
| cat > "$PKG/bin/openclaw.cmd" <<'EOF' | |
| @echo off | |
| setlocal | |
| set "HERE=%~dp0.." | |
| "%HERE%\tools\node\node.exe" "%HERE%\openclaw\openclaw.mjs" %* | |
| EOF | |
| fi | |
| # mac/win 用 .tar.gz(bsdtar 原生支持 gzip,小白机无 zstd 二进制);linux 用 .tar.zst(压缩率更好) | |
| if [[ "$plat" == linux-x64 ]]; then | |
| ASSET="xiaobei-${TAG}-${plat}.tar.zst" | |
| tar --zstd -cf "$RUNNER_TEMP/out/$ASSET" -C "$PKG" . | |
| else | |
| ASSET="xiaobei-${TAG}-${plat}.tar.gz" | |
| tar -czf "$RUNNER_TEMP/out/$ASSET" -C "$PKG" . | |
| fi | |
| ls -lh "$RUNNER_TEMP/out/$ASSET" | |
| done | |
| # 给后续步骤用 | |
| echo "out_dir=$RUNNER_TEMP/out" >> "$GITHUB_OUTPUT" | |
| echo "asset_tag=$ASSET_TAG" >> "$GITHUB_OUTPUT" | |
| ls -lh "$RUNNER_TEMP/out/" | |
| - name: Smoke test linux tarball (extract + install --skip-browser + openclaw --version) | |
| run: | | |
| set -euo pipefail | |
| ASSET_TAG="${{ steps.buildtars.outputs.asset_tag }}" | |
| if [[ "$ASSET_TAG" != v* ]]; then | |
| echo "无 release tag(asset_tag=$ASSET_TAG),跳过冒烟自检" | |
| exit 0 | |
| fi | |
| TB="$RUNNER_TEMP/out/xiaobei-$ASSET_TAG-linux-x64.tar.zst" | |
| [[ -f "$TB" ]] || { echo "❌ tarball 不存在:$TB"; exit 1; } | |
| SMOKE="$RUNNER_TEMP/smoke" | |
| rm -rf "$SMOKE" | |
| mkdir -p "$SMOKE/extract" "$SMOKE/program" "$SMOKE/runtime" | |
| # 解压一份拿到 scripts/install.sh(install.sh 会再按 XIAOBEI_TARBALL 解压到 program) | |
| tar --zstd -xf "$TB" -C "$SMOKE/extract" | |
| [[ -f "$SMOKE/extract/scripts/install.sh" ]] || { echo "❌ tarball 缺 scripts/install.sh"; exit 1; } | |
| export XIAOBEI_HOME="$SMOKE/program" | |
| export OPENCLAW_HOME="$SMOKE/runtime" | |
| export XIAOBEI_TARBALL="$TB" | |
| export XIAOBEI_TAG="$ASSET_TAG" | |
| export XIAOBEI_SOURCE=github | |
| export AWK_API_KEY="smoke-test-key" | |
| bash "$SMOKE/extract/scripts/install.sh" \ | |
| --no-prompt --skip-bind --skip-browser --root "$XIAOBEI_HOME" | |
| "$XIAOBEI_HOME/bin/openclaw" --version | |
| echo "✅ 冒烟自检通过:tarball 可解压 + 依赖可装 + 引擎可起" | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: xiaobei-tarballs | |
| path: | | |
| ${{ runner.temp }}/out/*.tar.zst | |
| ${{ runner.temp }}/out/*.tar.gz | |
| retention-days: 14 | |
| - name: Attach to release (on real tag, skip disttest) | |
| if: ${{ startsWith(steps.buildtars.outputs.asset_tag, 'v') && !contains(steps.buildtars.outputs.asset_tag, 'disttest') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${{ steps.buildtars.outputs.asset_tag }}" | |
| if ! gh release view "$TAG" >/dev/null 2>&1; then | |
| gh release create "$TAG" --title "$TAG" --generate-notes | |
| fi | |
| for f in "$RUNNER_TEMP"/out/*.tar.zst "$RUNNER_TEMP"/out/*.tar.gz; do | |
| [ -e "$f" ] || continue | |
| gh release upload "$TAG" "$f" --clobber | |
| done |