mirror of
https://github.com/actionbook/actionbook.git
synced 2026-09-19 03:28:06 +08:00
fa5b733781
* [root]chore: split CLI release into release-cli.yml and scope changeset to CLI only
- Rename .github/workflows/release.yml -> release-cli.yml, keeping only
version-or-release / build-cli / publish-cli jobs; drop publish-js,
release-extension, release-dify-plugin and their detection outputs
- Extend .changeset/config.json ignore list with JS packages, extension
and dify-plugin so `pnpm changeset` only proposes CLI-related packages
- Other package release pipelines will be re-introduced later as their
own independent workflows when needed
* [root]fix: silence shellcheck warnings in release-cli.yml
- SC2193: assign matrix.platform-suffix to a shell var before pattern
matching so shellcheck stops flagging the == win32-* comparison
- SC2086: switch TAG_FLAG string to a TAG_FLAGS bash array and expand
with "${TAG_FLAGS[@]}" in both platform and CLI publish steps
504 lines
18 KiB
YAML
504 lines
18 KiB
YAML
name: Release CLI
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
workflow_dispatch:
|
|
|
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
id-token: write
|
|
|
|
jobs:
|
|
# --------------------------------------------------------------------------
|
|
# Job 1: Create Version PR or determine whether CLI needs a release
|
|
# --------------------------------------------------------------------------
|
|
version-or-release:
|
|
name: Version or Release
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should-release: ${{ steps.changesets.outputs.hasChangesets == 'false' }}
|
|
cli-needs-release: ${{ steps.check-releases.outputs.cli }}
|
|
cli-changed: ${{ steps.changes.outputs.cli }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
registry-url: "https://registry.npmjs.org"
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Detect changed packages
|
|
id: changes
|
|
run: |
|
|
# workflow_dispatch or first push: treat CLI as changed
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ] || \
|
|
[ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then
|
|
echo "cli=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
CHANGED=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}")
|
|
echo "Changed files:"
|
|
echo "$CHANGED"
|
|
|
|
if echo "$CHANGED" | grep -qE "^packages/cli[-/]"; then
|
|
echo "cli=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "cli=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Create Release Pull Request
|
|
id: changesets
|
|
uses: changesets/action@v1
|
|
with:
|
|
version: pnpm version-packages
|
|
commit: "chore: version packages"
|
|
title: "chore: version packages"
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Check whether CLI needs release
|
|
id: check-releases
|
|
if: steps.changesets.outputs.hasChangesets == 'false'
|
|
run: |
|
|
CLI_LOCAL=$(node -e "console.log(JSON.parse(require('fs').readFileSync('packages/cli/package.json','utf8')).version)")
|
|
CLI_NPM=$(npm view @actionbookdev/cli version 2>/dev/null || echo "0.0.0")
|
|
if [ "$CLI_LOCAL" != "$CLI_NPM" ]; then
|
|
echo "cli=true" >> "$GITHUB_OUTPUT"
|
|
echo "CLI needs release: $CLI_LOCAL (local) vs $CLI_NPM (npm)"
|
|
else
|
|
echo "cli=false" >> "$GITHUB_OUTPUT"
|
|
echo "CLI is up to date: $CLI_LOCAL"
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Job 2: Build CLI binaries (cross-platform Rust compilation)
|
|
# --------------------------------------------------------------------------
|
|
build-cli:
|
|
name: Build CLI (${{ matrix.platform-suffix }})
|
|
needs: version-or-release
|
|
if: needs.version-or-release.outputs.cli-needs-release == 'true' && needs.version-or-release.outputs.cli-changed == 'true'
|
|
strategy:
|
|
matrix:
|
|
include:
|
|
- target: aarch64-apple-darwin
|
|
os: macos-latest
|
|
platform-suffix: darwin-arm64
|
|
- target: x86_64-apple-darwin
|
|
os: macos-latest
|
|
platform-suffix: darwin-x64
|
|
- target: x86_64-unknown-linux-gnu
|
|
os: ubuntu-22.04
|
|
platform-suffix: linux-x64
|
|
- target: aarch64-unknown-linux-gnu
|
|
os: ubuntu-latest
|
|
platform-suffix: linux-arm64
|
|
use-cross: true
|
|
- target: x86_64-pc-windows-msvc
|
|
os: windows-latest
|
|
platform-suffix: win32-x64
|
|
- target: aarch64-pc-windows-msvc
|
|
os: windows-latest
|
|
platform-suffix: win32-arm64
|
|
|
|
runs-on: ${{ matrix.os }}
|
|
defaults:
|
|
run:
|
|
working-directory: packages/cli
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- name: Install cross (Linux ARM64)
|
|
if: matrix.use-cross
|
|
run: cargo install cross --git https://github.com/cross-rs/cross
|
|
|
|
- name: Build (cross)
|
|
if: matrix.use-cross
|
|
working-directory: .
|
|
run: cross build --release --target ${{ matrix.target }} --manifest-path packages/cli/Cargo.toml
|
|
|
|
- name: Build (native)
|
|
if: ${{ !matrix.use-cross }}
|
|
run: cargo build --release --target ${{ matrix.target }}
|
|
|
|
- name: Determine binary name
|
|
id: binary
|
|
shell: bash
|
|
run: |
|
|
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
|
|
echo "name=actionbook.exe" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "name=actionbook" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Rename binary for platform
|
|
shell: bash
|
|
run: |
|
|
CANDIDATES=(
|
|
"target/${{ matrix.target }}/release/${{ steps.binary.outputs.name }}"
|
|
"../target/${{ matrix.target }}/release/${{ steps.binary.outputs.name }}"
|
|
)
|
|
SRC=""
|
|
for candidate in "${CANDIDATES[@]}"; do
|
|
if [[ -f "$candidate" ]]; then
|
|
SRC="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -z "$SRC" ]]; then
|
|
echo "ERROR: Built binary not found in expected target directories."
|
|
printf 'Checked:\n - %s\n' "${CANDIDATES[@]}"
|
|
exit 1
|
|
fi
|
|
|
|
ARTIFACT_DIR="${RUNNER_TEMP}/actionbook-binaries"
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
|
|
PLATFORM_SUFFIX="${{ matrix.platform-suffix }}"
|
|
if [[ "$PLATFORM_SUFFIX" == win32-* ]]; then
|
|
DEST="$ARTIFACT_DIR/actionbook-${PLATFORM_SUFFIX}.exe"
|
|
else
|
|
DEST="$ARTIFACT_DIR/actionbook-${PLATFORM_SUFFIX}"
|
|
fi
|
|
cp "$SRC" "$DEST"
|
|
chmod +x "$DEST"
|
|
|
|
- name: Upload artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: actionbook-${{ matrix.platform-suffix }}
|
|
path: ${{ runner.temp }}/actionbook-binaries/actionbook-${{ matrix.platform-suffix }}*
|
|
if-no-files-found: error
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Job 3: Publish CLI npm packages + GitHub Release
|
|
# --------------------------------------------------------------------------
|
|
publish-cli:
|
|
name: Publish CLI
|
|
needs: [version-or-release, build-cli]
|
|
if: needs.version-or-release.outputs.cli-needs-release == 'true' && needs.version-or-release.outputs.cli-changed == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
registry-url: "https://registry.npmjs.org"
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Read CLI version
|
|
id: version
|
|
run: |
|
|
VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('packages/cli/package.json','utf8')).version)")
|
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Prepare platform package binaries
|
|
run: |
|
|
declare -A PLATFORM_PKG_DIRS=(
|
|
["darwin-arm64"]="packages/cli-darwin-arm64"
|
|
["darwin-x64"]="packages/cli-darwin-x64"
|
|
["linux-x64"]="packages/cli-linux-x64-gnu"
|
|
["linux-arm64"]="packages/cli-linux-arm64-gnu"
|
|
["win32-x64"]="packages/cli-win32-x64"
|
|
["win32-arm64"]="packages/cli-win32-arm64"
|
|
)
|
|
|
|
for suffix in "${!PLATFORM_PKG_DIRS[@]}"; do
|
|
artifact_dir="artifacts/actionbook-${suffix}"
|
|
package_dir="${PLATFORM_PKG_DIRS[$suffix]}"
|
|
|
|
if [[ "$suffix" == win32-* ]]; then
|
|
src_name="actionbook-${suffix}.exe"
|
|
dest_name="actionbook.exe"
|
|
else
|
|
src_name="actionbook-${suffix}"
|
|
dest_name="actionbook"
|
|
fi
|
|
|
|
cp "${artifact_dir}/${src_name}" "${package_dir}/bin/${dest_name}"
|
|
chmod +x "${package_dir}/bin/${dest_name}"
|
|
echo "Copied ${src_name} -> ${package_dir}/bin/${dest_name}"
|
|
done
|
|
|
|
- name: Validate platform binaries
|
|
run: |
|
|
declare -A PLATFORM_PKG_DIRS=(
|
|
["darwin-arm64"]="packages/cli-darwin-arm64"
|
|
["darwin-x64"]="packages/cli-darwin-x64"
|
|
["linux-x64"]="packages/cli-linux-x64-gnu"
|
|
["linux-arm64"]="packages/cli-linux-arm64-gnu"
|
|
["win32-x64"]="packages/cli-win32-x64"
|
|
["win32-arm64"]="packages/cli-win32-arm64"
|
|
)
|
|
min_size=100000
|
|
failures=0
|
|
|
|
for suffix in "${!PLATFORM_PKG_DIRS[@]}"; do
|
|
package_dir="${PLATFORM_PKG_DIRS[$suffix]}"
|
|
|
|
if [[ "$suffix" == win32-* ]]; then
|
|
binary_path="${package_dir}/bin/actionbook.exe"
|
|
else
|
|
binary_path="${package_dir}/bin/actionbook"
|
|
fi
|
|
|
|
if [[ ! -f "${binary_path}" ]]; then
|
|
echo "ERROR: Missing binary ${binary_path}"
|
|
failures=$((failures + 1))
|
|
continue
|
|
fi
|
|
|
|
size=$(stat -c%s "${binary_path}")
|
|
if [[ "$size" -lt "$min_size" ]]; then
|
|
echo "ERROR: Binary too small ${binary_path} (${size} bytes)"
|
|
failures=$((failures + 1))
|
|
continue
|
|
fi
|
|
|
|
echo "OK: ${binary_path} (${size} bytes)"
|
|
done
|
|
|
|
if [[ "$failures" -gt 0 ]]; then
|
|
echo "Found ${failures} platform binary validation errors"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Generate SHA256 checksums for CLI binaries
|
|
run: |
|
|
EXPECTED_ASSETS=(
|
|
"actionbook-darwin-arm64"
|
|
"actionbook-darwin-x64"
|
|
"actionbook-linux-x64"
|
|
"actionbook-linux-arm64"
|
|
"actionbook-win32-x64.exe"
|
|
"actionbook-win32-arm64.exe"
|
|
)
|
|
|
|
missing=0
|
|
for asset in "${EXPECTED_ASSETS[@]}"; do
|
|
artifact_dir="${asset%.exe}"
|
|
binary_path="artifacts/${artifact_dir}/${asset}"
|
|
if [[ ! -f "$binary_path" ]]; then
|
|
echo "ERROR: Expected binary not found: $binary_path"
|
|
missing=$((missing + 1))
|
|
fi
|
|
done
|
|
if [[ "$missing" -gt 0 ]]; then
|
|
echo "Missing $missing expected binaries — aborting checksum generation"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p artifacts/checksums
|
|
: > artifacts/checksums/SHA256SUMS
|
|
for asset in "${EXPECTED_ASSETS[@]}"; do
|
|
artifact_dir="${asset%.exe}"
|
|
binary_path="artifacts/${artifact_dir}/${asset}"
|
|
checksum=$(sha256sum "$binary_path" | awk '{print $1}')
|
|
printf '%s %s\n' "$checksum" "$asset" >> artifacts/checksums/SHA256SUMS
|
|
done
|
|
|
|
echo "--- SHA256SUMS ---"
|
|
cat artifacts/checksums/SHA256SUMS
|
|
|
|
- name: Publish platform packages
|
|
env:
|
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
run: |
|
|
declare -A PLATFORM_PKG_DIRS=(
|
|
["darwin-arm64"]="packages/cli-darwin-arm64"
|
|
["darwin-x64"]="packages/cli-darwin-x64"
|
|
["linux-x64"]="packages/cli-linux-x64-gnu"
|
|
["linux-arm64"]="packages/cli-linux-arm64-gnu"
|
|
["win32-x64"]="packages/cli-win32-x64"
|
|
["win32-arm64"]="packages/cli-win32-arm64"
|
|
)
|
|
declare -A PLATFORM_PKG_NAMES=(
|
|
["darwin-arm64"]="@actionbookdev/cli-darwin-arm64"
|
|
["darwin-x64"]="@actionbookdev/cli-darwin-x64"
|
|
["linux-x64"]="@actionbookdev/cli-linux-x64-gnu"
|
|
["linux-arm64"]="@actionbookdev/cli-linux-arm64-gnu"
|
|
["win32-x64"]="@actionbookdev/cli-win32-x64"
|
|
["win32-arm64"]="@actionbookdev/cli-win32-arm64"
|
|
)
|
|
|
|
for suffix in darwin-arm64 darwin-x64 linux-x64 linux-arm64 win32-x64 win32-arm64; do
|
|
pkg_dir="${PLATFORM_PKG_DIRS[$suffix]}"
|
|
pkg_name="${PLATFORM_PKG_NAMES[$suffix]}"
|
|
LOCAL_VER=$(node -e "console.log(JSON.parse(require('fs').readFileSync('${pkg_dir}/package.json','utf8')).version)")
|
|
NPM_VER=$(npm view "${pkg_name}" version 2>/dev/null || echo "0.0.0")
|
|
if [ "$LOCAL_VER" != "$NPM_VER" ]; then
|
|
TAG_FLAGS=()
|
|
if [[ "$LOCAL_VER" == *-alpha* ]]; then
|
|
TAG_FLAGS=(--tag alpha)
|
|
fi
|
|
echo "Publishing ${pkg_name}@${LOCAL_VER} (npm has ${NPM_VER})..."
|
|
(cd "${pkg_dir}" && npm publish --access public --provenance "${TAG_FLAGS[@]}")
|
|
else
|
|
echo "Skipping ${pkg_name}@${LOCAL_VER} (already published)"
|
|
fi
|
|
done
|
|
|
|
- name: Replace workspace protocol in CLI package
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
node -e "
|
|
const fs = require('fs');
|
|
const pkg = JSON.parse(fs.readFileSync('packages/cli/package.json', 'utf8'));
|
|
for (const [name, ver] of Object.entries(pkg.optionalDependencies || {})) {
|
|
if (ver.startsWith('workspace:')) {
|
|
pkg.optionalDependencies[name] = '${VERSION}';
|
|
}
|
|
}
|
|
fs.writeFileSync('packages/cli/package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
console.log('Replaced workspace:* with ${VERSION} in CLI optionalDependencies');
|
|
"
|
|
|
|
- name: Copy root README to CLI package
|
|
run: cp README.md packages/cli/README.md
|
|
|
|
- name: Publish CLI package
|
|
env:
|
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
run: |
|
|
LOCAL_VER="${{ steps.version.outputs.version }}"
|
|
NPM_VER=$(npm view @actionbookdev/cli version 2>/dev/null || echo "0.0.0")
|
|
if [ "$LOCAL_VER" != "$NPM_VER" ]; then
|
|
TAG_FLAGS=()
|
|
if [[ "$LOCAL_VER" == *-alpha* ]]; then
|
|
TAG_FLAGS=(--tag alpha)
|
|
fi
|
|
echo "Publishing @actionbookdev/cli@${LOCAL_VER} (npm has ${NPM_VER})..."
|
|
(cd packages/cli && npm publish --access public --provenance "${TAG_FLAGS[@]}")
|
|
else
|
|
echo "Skipping @actionbookdev/cli@${LOCAL_VER} (already published)"
|
|
fi
|
|
|
|
- name: Create CLI GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
token: ${{ secrets.RELEASE_PAT || github.token }}
|
|
tag_name: actionbook-cli-v${{ steps.version.outputs.version }}
|
|
name: Actionbook CLI v${{ steps.version.outputs.version }}
|
|
files: |
|
|
artifacts/actionbook-darwin-arm64/actionbook-darwin-arm64
|
|
artifacts/actionbook-darwin-x64/actionbook-darwin-x64
|
|
artifacts/actionbook-linux-x64/actionbook-linux-x64
|
|
artifacts/actionbook-linux-arm64/actionbook-linux-arm64
|
|
artifacts/actionbook-win32-x64/actionbook-win32-x64.exe
|
|
artifacts/actionbook-win32-arm64/actionbook-win32-arm64.exe
|
|
artifacts/checksums/SHA256SUMS
|
|
generate_release_notes: true
|
|
|
|
- name: Update Homebrew formula
|
|
env:
|
|
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
TAG="actionbook-cli-v${VERSION}"
|
|
|
|
# Read SHA256SUMS generated earlier in this job
|
|
CHECKSUMS_FILE="artifacts/checksums/SHA256SUMS"
|
|
if [[ ! -f "$CHECKSUMS_FILE" ]]; then
|
|
echo "WARNING: SHA256SUMS not found, skipping Homebrew formula update"
|
|
exit 0
|
|
fi
|
|
|
|
get_sha() {
|
|
local asset="$1"
|
|
grep " ${asset}$" "$CHECKSUMS_FILE" | cut -d' ' -f1
|
|
}
|
|
|
|
SHA_DARWIN_ARM64=$(get_sha "actionbook-darwin-arm64")
|
|
SHA_DARWIN_X64=$(get_sha "actionbook-darwin-x64")
|
|
SHA_LINUX_ARM64=$(get_sha "actionbook-linux-arm64")
|
|
SHA_LINUX_X64=$(get_sha "actionbook-linux-x64")
|
|
|
|
# Generate the updated formula
|
|
cat > /tmp/actionbook.rb << RUBY_EOF
|
|
# typed: false
|
|
# frozen_string_literal: true
|
|
|
|
# Formula auto-updated by actionbook/actionbook release CI.
|
|
# Manual edits will be overwritten on the next release.
|
|
class Actionbook < Formula
|
|
desc "CLI for Actionbook - run AI actions from your terminal"
|
|
homepage "https://actionbook.dev"
|
|
version "${VERSION}"
|
|
license "MIT"
|
|
|
|
on_macos do
|
|
if Hardware::CPU.arm?
|
|
url "https://github.com/actionbook/actionbook/releases/download/${TAG}/actionbook-darwin-arm64"
|
|
sha256 "${SHA_DARWIN_ARM64}"
|
|
else
|
|
url "https://github.com/actionbook/actionbook/releases/download/${TAG}/actionbook-darwin-x64"
|
|
sha256 "${SHA_DARWIN_X64}"
|
|
end
|
|
end
|
|
|
|
on_linux do
|
|
if Hardware::CPU.arm?
|
|
url "https://github.com/actionbook/actionbook/releases/download/${TAG}/actionbook-linux-arm64"
|
|
sha256 "${SHA_LINUX_ARM64}"
|
|
else
|
|
url "https://github.com/actionbook/actionbook/releases/download/${TAG}/actionbook-linux-x64"
|
|
sha256 "${SHA_LINUX_X64}"
|
|
end
|
|
end
|
|
|
|
def install
|
|
binary_name = "actionbook"
|
|
if OS.mac?
|
|
bin.install (Hardware::CPU.arm? ? "actionbook-darwin-arm64" : "actionbook-darwin-x64") => binary_name
|
|
else
|
|
bin.install (Hardware::CPU.arm? ? "actionbook-linux-arm64" : "actionbook-linux-x64") => binary_name
|
|
end
|
|
end
|
|
|
|
test do
|
|
assert_match version.to_s, shell_output("#{bin}/actionbook --version")
|
|
end
|
|
end
|
|
RUBY_EOF
|
|
|
|
# Remove leading whitespace (heredoc indentation)
|
|
sed -i "s/^ //" /tmp/actionbook.rb
|
|
|
|
# Push to homebrew-tap repo
|
|
cd "$(mktemp -d)"
|
|
git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/actionbook/homebrew-tap.git" .
|
|
cp /tmp/actionbook.rb Formula/actionbook.rb
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git add Formula/actionbook.rb
|
|
git commit -m "actionbook ${VERSION}" || echo "No changes to commit"
|
|
git push
|