Files
codeg/.github/workflows/release.yml
xintaofei 3b331b15a4 fix(ci): replace softprops/action-gh-release with gh CLI for server asset upload
softprops/action-gh-release defaults draft to false, which publishes
the release prematurely before the publish-release job runs. Switch to
gh release upload which only uploads assets without modifying release
state, avoiding both the premature publish and the duplicate draft
release issues caused by the GitHub API not returning drafts via
getReleaseByTag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:55:18 +08:00

568 lines
21 KiB
YAML

name: Release
on:
push:
tags:
- "v*.*.*"
env:
RUST_BACKTRACE: short
HUSKY: 0
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
create-draft-release:
runs-on: ubuntu-22.04
permissions:
contents: write
outputs:
release_id: ${{ steps.release.outputs.release_id }}
release_url: ${{ steps.release.outputs.release_url }}
prerelease: ${{ steps.meta.outputs.prerelease }}
release_body: ${{ steps.release.outputs.release_body }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: meta
name: Resolve release channel
shell: bash
run: |
if [[ "${GITHUB_REF_NAME}" == *"-rc"* ]]; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi
- name: Check tag commit belongs to default branch
shell: bash
run: |
DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
git fetch origin "${DEFAULT_BRANCH}"
TAG_COMMIT=$(git rev-list -n 1 "${GITHUB_REF_NAME}")
if git merge-base --is-ancestor "${TAG_COMMIT}" "origin/${DEFAULT_BRANCH}"; then
echo "Tag ${GITHUB_REF_NAME} is based on ${DEFAULT_BRANCH}"
else
echo "Tag ${GITHUB_REF_NAME} is not based on ${DEFAULT_BRANCH}"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Verify tag matches app version
shell: bash
run: |
APP_VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")
if [[ "v$APP_VERSION" != "${GITHUB_REF_NAME}" ]]; then
echo "Tag ${GITHUB_REF_NAME} does not match app version v$APP_VERSION"
exit 1
fi
- id: release
name: Create or reuse draft release
uses: actions/github-script@v7
env:
PRERELEASE: ${{ steps.meta.outputs.prerelease }}
with:
script: |
const tag = context.ref.replace("refs/tags/", "");
const { owner, repo } = context.repo;
const prerelease = process.env.PRERELEASE === "true";
const releaseName = `codeg ${tag}`;
const { data: tagRef } = await github.rest.git.getRef({
owner,
repo,
ref: `tags/${tag}`,
});
let commitSha = tagRef.object.sha;
if (tagRef.object.type === "tag") {
const { data: annotatedTag } = await github.rest.git.getTag({
owner,
repo,
tag_sha: commitSha,
});
if (annotatedTag.object.type !== "commit") {
core.setFailed(
`Tag ${tag} points to ${annotatedTag.object.type}, not a commit.`,
);
return;
}
commitSha = annotatedTag.object.sha;
}
const { data: commit } = await github.rest.repos.getCommit({
owner,
repo,
ref: commitSha,
});
const releaseBody =
commit.commit.message?.trim() || "_No commit message._";
let release;
try {
const existing = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag,
});
if (!existing.data.draft) {
core.setFailed(
`Release for tag ${tag} already exists and is not a draft.`,
);
return;
}
release = existing.data;
if (
release.prerelease !== prerelease ||
release.name !== releaseName ||
(release.body ?? "").trim() !== releaseBody
) {
const updated = await github.rest.repos.updateRelease({
owner,
repo,
release_id: release.id,
name: releaseName,
prerelease,
body: releaseBody,
});
release = updated.data;
}
core.info(`Reusing existing draft release #${release.id}`);
} catch (error) {
if (error.status !== 404) {
throw error;
}
const created = await github.rest.repos.createRelease({
owner,
repo,
tag_name: tag,
name: releaseName,
body: releaseBody,
draft: true,
prerelease,
});
release = created.data;
core.info(`Created draft release #${release.id}`);
}
core.setOutput("release_id", String(release.id));
core.setOutput("release_url", release.html_url);
core.setOutput("release_body", releaseBody);
build-tauri:
needs: create-draft-release
name: Build ${{ matrix.name }}
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- name: "macOS x64"
runner: "macos-latest"
target: "x86_64-apple-darwin"
- name: "macOS arm64"
runner: "macos-latest"
target: "aarch64-apple-darwin"
- name: "Linux x64"
runner: "ubuntu-22.04"
target: "x86_64-unknown-linux-gnu"
- name: "Linux arm64"
runner: "ubuntu-22.04"
target: "aarch64-unknown-linux-gnu"
- name: "Windows x64"
runner: "windows-2022"
target: "x86_64-pc-windows-msvc"
- name: "Windows arm64"
runner: "windows-latest"
target: "aarch64-pc-windows-msvc"
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target"
shared-key: ${{ matrix.target }}
- name: Install Linux x64 dependencies
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf
- name: Install Linux arm64 cross dependencies
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
cat > /tmp/sources.list << 'EOF'
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse
deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse
EOF
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture arm64
sudo apt-get update
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
libwebkit2gtk-4.1-dev:arm64 \
libayatana-appindicator3-dev:arm64 \
librsvg2-dev:arm64 \
libssl-dev:arm64 \
patchelf
- name: Configure Linux arm64 cross env
if: matrix.target == 'aarch64-unknown-linux-gnu'
shell: bash
run: |
echo "PKG_CONFIG_ALLOW_CROSS=1" >> "$GITHUB_ENV"
echo "PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV"
echo "PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV"
echo "PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu" >> "$GITHUB_ENV"
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_AR=aarch64-linux-gnu-gcc-ar" >> "$GITHUB_ENV"
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=-Clinker=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
echo "CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++" >> "$GITHUB_ENV"
echo "AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc-ar" >> "$GITHUB_ENV"
- name: Verify Linux arm64 cross toolchain
if: matrix.target == 'aarch64-unknown-linux-gnu'
shell: bash
run: |
which aarch64-linux-gnu-gcc
aarch64-linux-gnu-gcc -v
rustup target list --installed
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=${CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER}"
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_AR=${CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_AR}"
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=${CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS}"
- name: Install frontend dependencies
run: pnpm install --frozen-lockfile
- name: Build and upload to draft release (Linux arm64)
if: matrix.target == 'aarch64-unknown-linux-gnu'
uses: tauri-apps/tauri-action@v0.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
PKG_CONFIG_ALLOW_CROSS: 1
PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig
PKG_CONFIG_LIBDIR: /usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig
PKG_CONFIG_SYSROOT_DIR: /usr/aarch64-linux-gnu
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_AR: aarch64-linux-gnu-gcc-ar
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -Clinker=aarch64-linux-gnu-gcc
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
AR_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc-ar
with:
releaseId: ${{ needs.create-draft-release.outputs.release_id }}
tagName: ${{ github.ref_name }}
releaseBody: ${{ needs.create-draft-release.outputs.release_body }}
releaseDraft: true
prerelease: ${{ needs.create-draft-release.outputs.prerelease }}
tauriScript: pnpm tauri
args: --target ${{ matrix.target }} --bundles deb,rpm
includeUpdaterJson: false
retryAttempts: 2
- name: Build and upload to draft release (Non-Linux arm64)
if: matrix.target != 'aarch64-unknown-linux-gnu'
uses: tauri-apps/tauri-action@v0.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
releaseId: ${{ needs.create-draft-release.outputs.release_id }}
tagName: ${{ github.ref_name }}
releaseBody: ${{ needs.create-draft-release.outputs.release_body }}
releaseDraft: true
prerelease: ${{ needs.create-draft-release.outputs.prerelease }}
tauriScript: pnpm tauri
args: --target ${{ matrix.target }}
includeUpdaterJson: true
retryAttempts: 2
build-server:
needs: create-draft-release
name: Server ${{ matrix.name }}
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- name: "Linux x64"
runner: "ubuntu-22.04"
target: "x86_64-unknown-linux-gnu"
artifact: "codeg-server-linux-x64"
- name: "Linux arm64"
runner: "ubuntu-22.04"
target: "aarch64-unknown-linux-gnu"
artifact: "codeg-server-linux-arm64"
- name: "macOS x64"
runner: "macos-latest"
target: "x86_64-apple-darwin"
artifact: "codeg-server-darwin-x64"
- name: "macOS arm64"
runner: "macos-latest"
target: "aarch64-apple-darwin"
artifact: "codeg-server-darwin-arm64"
- name: "Windows x64"
runner: "windows-2022"
target: "x86_64-pc-windows-msvc"
artifact: "codeg-server-windows-x64"
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target"
shared-key: server-${{ matrix.target }}
- name: Install Linux arm64 cross compiler
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
cat > /tmp/sources.list << 'EOF'
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse
deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse
EOF
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture arm64
sudo apt-get update
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
libssl-dev:arm64
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
echo "PKG_CONFIG_ALLOW_CROSS=1" >> "$GITHUB_ENV"
echo "PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV"
echo "PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV"
echo "PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu" >> "$GITHUB_ENV"
echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
echo "CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++" >> "$GITHUB_ENV"
echo "AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc-ar" >> "$GITHUB_ENV"
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Build frontend
run: |
pnpm install --frozen-lockfile
pnpm build
- name: Build server binary
working-directory: src-tauri
run: cargo build --release --bin codeg-server --no-default-features --target ${{ matrix.target }}
- name: Package (Unix)
if: runner.os != 'Windows'
run: |
mkdir -p dist/${{ matrix.artifact }}
cp src-tauri/target/${{ matrix.target }}/release/codeg-server dist/${{ matrix.artifact }}/
cp -r out dist/${{ matrix.artifact }}/web
cd dist && tar czf ${{ matrix.artifact }}.tar.gz ${{ matrix.artifact }}
- name: Package (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist/${{ matrix.artifact }}
Copy-Item src-tauri/target/${{ matrix.target }}/release/codeg-server.exe dist/${{ matrix.artifact }}/
Copy-Item -Recurse out dist/${{ matrix.artifact }}/web
Compress-Archive -Path dist/${{ matrix.artifact }} -DestinationPath dist/${{ matrix.artifact }}.zip
- name: Upload artifact for Docker build (Linux only)
if: startsWith(matrix.target, 'x86_64-unknown-linux') || startsWith(matrix.target, 'aarch64-unknown-linux')
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: dist/${{ matrix.artifact }}
retention-days: 1
- name: Upload to release (Unix)
if: runner.os != 'Windows'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "${{ github.ref_name }}" dist/${{ matrix.artifact }}.tar.gz --clobber
- name: Upload to release (Windows)
if: runner.os == 'Windows'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "${{ github.ref_name }}" dist/${{ matrix.artifact }}.zip --clobber
build-docker:
needs:
- create-draft-release
- build-server
name: Build Docker image
runs-on: ubuntu-22.04
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Download Linux x64 artifact
uses: actions/download-artifact@v4
with:
name: codeg-server-linux-x64
path: artifacts/linux-x64
- name: Download Linux arm64 artifact
uses: actions/download-artifact@v4
with:
name: codeg-server-linux-arm64
path: artifacts/linux-arm64
- name: Prepare Docker build context
run: |
mkdir -p dist/amd64 dist/arm64
cp artifacts/linux-x64/codeg-server dist/amd64/codeg-server
cp artifacts/linux-arm64/codeg-server dist/arm64/codeg-server
cp -r artifacts/linux-x64/web dist/web
chmod +x dist/amd64/codeg-server dist/arm64/codeg-server
- name: Set up QEMU (for multi-arch manifest)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.ci
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}
ghcr.io/${{ github.repository }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/codeg:${{ steps.version.outputs.version }}
${{ secrets.DOCKERHUB_USERNAME }}/codeg:latest
publish-release:
needs:
- create-draft-release
- build-tauri
- build-server
- build-docker
if: ${{ needs.build-tauri.result == 'success' && needs.build-server.result == 'success' && needs.build-docker.result == 'success' }}
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Publish draft release
uses: actions/github-script@v7
env:
RELEASE_ID: ${{ needs.create-draft-release.outputs.release_id }}
with:
script: |
const { owner, repo } = context.repo;
const releaseId = Number(process.env.RELEASE_ID);
const prerelease =
"${{ needs.create-draft-release.outputs.prerelease }}" === "true";
await github.rest.repos.updateRelease({
owner,
repo,
release_id: releaseId,
draft: false,
prerelease,
});
core.info(`Published release #${releaseId}`);