Skip to content

Release

Release #47

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g. v0.5.5 v0.5.6-beta.1)'
required: true
type: string
push:
tags:
- v*.*.*
permissions:
contents: write
jobs:
resolve-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.resolve.outputs.tag }}
sha: ${{ steps.resolve.outputs.sha }}
steps:
- name: Resolve tag
id: resolve
uses: actions/github-script@v7
with:
script: |
const isDispatch = context.eventName === 'workflow_dispatch'
const tag = isDispatch
? context.payload.inputs?.tag
: context.ref.replace('refs/tags/', '')
if (!tag) {
core.setFailed('Tag is required')
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const refName = `tags/${tag}`
const resolveTagSha = async (refData) => {
let sha = refData.object.sha
if (refData.object.type === 'tag') {
const tagObj = await github.rest.git.getTag({
owner,
repo,
tag_sha: sha
})
sha = tagObj.data.object.sha
}
return sha
}
if (isDispatch) {
try {
const { data } = await github.rest.git.getRef({
owner,
repo,
ref: refName
})
const sha = await resolveTagSha(data)
core.setOutput('sha', sha)
} catch (error) {
const sha = context.sha
await github.rest.git.createRef({
owner,
repo,
ref: `refs/${refName}`,
sha
})
core.setOutput('sha', sha)
}
} else {
try {
const { data } = await github.rest.git.getRef({
owner,
repo,
ref: refName
})
const sha = await resolveTagSha(data)
core.setOutput('sha', sha)
} catch (error) {
core.setFailed(`Tag ${tag} not found`)
return
}
}
core.setOutput('tag', tag)
build-windows:
needs: resolve-tag
runs-on: windows-latest
strategy:
matrix:
arch: [x64]
include:
- arch: x64
platform: win-x64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-tag.outputs.sha }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.12.1
- name: Install dependencies
run: pnpm install
- name: Configure pnpm workspace for Windows ${{ matrix.arch }}
run: pnpm run install:sharp
env:
TARGET_OS: win32
TARGET_ARCH: ${{ matrix.arch }}
- name: Install dependencies
run: pnpm install
env:
npm_config_build_from_source: true
npm_config_platform: win32
npm_config_arch: ${{ matrix.arch }}
- name: Install Node Runtime
run: pnpm run installRuntime:win:${{ matrix.arch }}
- name: Build Windows
run: |
pnpm run build
pnpm exec electron-builder --win --${{ matrix.arch }} --publish=never
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_GITHUB_CLIENT_ID: ${{ secrets.DC_GITHUB_CLIENT_ID }}
VITE_GITHUB_CLIENT_SECRET: ${{ secrets.DC_GITHUB_CLIENT_SECRET }}
VITE_GITHUB_REDIRECT_URI: ${{ secrets.DC_GITHUB_REDIRECT_URI }}
VITE_PROVIDER_DB_URL: ${{ secrets.CDN_PROVIDER_DB_URL }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: deepchat-${{ matrix.platform }}
path: |
dist/*
!dist/win-unpacked
!dist/win-arm64-unpacked
build-linux:
needs: resolve-tag
runs-on: ubuntu-22.04
strategy:
matrix:
arch: [x64]
include:
- arch: x64
platform: linux-x64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-tag.outputs.sha }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.12.1
- name: Install dependencies
run: pnpm install
- name: Configure pnpm workspace for Linux ${{ matrix.arch }}
run: pnpm run install:sharp
env:
TARGET_OS: linux
TARGET_ARCH: ${{ matrix.arch }}
- name: Install dependencies
run: pnpm install
- name: Build Linux
run: |
pnpm run build
pnpm exec electron-builder --linux --${{ matrix.arch }} --publish=never
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_GITHUB_CLIENT_ID: ${{ secrets.DC_GITHUB_CLIENT_ID }}
VITE_GITHUB_CLIENT_SECRET: ${{ secrets.DC_GITHUB_CLIENT_SECRET }}
VITE_GITHUB_REDIRECT_URI: ${{ secrets.DC_GITHUB_REDIRECT_URI }}
VITE_PROVIDER_DB_URL: ${{ secrets.CDN_PROVIDER_DB_URL }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: deepchat-${{ matrix.platform }}
path: |
dist/*
!dist/linux-unpacked
build-mac:
needs: resolve-tag
runs-on: macos-15
strategy:
matrix:
arch: [x64, arm64]
include:
- arch: x64
platform: mac-x64
- arch: arm64
platform: mac-arm64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-tag.outputs.sha }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.12.1
- name: Install dependencies
run: pnpm install
- name: Configure pnpm workspace for macOS ${{ matrix.arch }}
run: pnpm run install:sharp
env:
TARGET_OS: darwin
TARGET_ARCH: ${{ matrix.arch }}
- name: Install dependencies
run: pnpm install
- name: Install Node Runtime
run: pnpm run installRuntime:mac:${{ matrix.arch }}
- name: Build Mac
run: |
pnpm run build
pnpm exec electron-builder --mac --${{ matrix.arch }} --publish=never
env:
CSC_LINK: ${{ secrets.DEEPCHAT_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.DEEPCHAT_CSC_KEY_PASS }}
DEEPCHAT_APPLE_NOTARY_USERNAME: ${{ secrets.DEEPCHAT_APPLE_NOTARY_USERNAME }}
DEEPCHAT_APPLE_NOTARY_TEAM_ID: ${{ secrets.DEEPCHAT_APPLE_NOTARY_TEAM_ID }}
DEEPCHAT_APPLE_NOTARY_PASSWORD: ${{ secrets.DEEPCHAT_APPLE_NOTARY_PASSWORD }}
build_for_release: '2'
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_GITHUB_CLIENT_ID: ${{ secrets.DC_GITHUB_CLIENT_ID }}
VITE_GITHUB_CLIENT_SECRET: ${{ secrets.DC_GITHUB_CLIENT_SECRET }}
VITE_GITHUB_REDIRECT_URI: ${{ secrets.DC_GITHUB_REDIRECT_URI }}
NODE_OPTIONS: '--max-old-space-size=4096'
VITE_PROVIDER_DB_URL: ${{ secrets.CDN_PROVIDER_DB_URL }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: deepchat-${{ matrix.platform }}
path: |
dist/*
!dist/mac/*
!dist/mac-arm64/*
release:
needs:
- resolve-tag
- build-windows
- build-linux
- build-mac
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-tag.outputs.sha }}
fetch-depth: 1
- name: Get version number
id: get_version
run: |
VERSION=$(node -p "require('./package.json').version")
TAG="${{ needs.resolve-tag.outputs.tag }}"
if [ "v$VERSION" != "$TAG" ]; then
echo "Error: tag $TAG does not match package.json version v$VERSION"
exit 1
fi
if echo "$VERSION" | grep -qE '-(beta|alpha)\.[0-9]+$'; then
echo "prerelease=true" >> $GITHUB_OUTPUT
else
echo "prerelease=false" >> $GITHUB_OUTPUT
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build release notes from CHANGELOG
run: |
VERSION="${{ steps.get_version.outputs.version }}"
CHANGELOG="CHANGELOG.md"
if [ ! -f "$CHANGELOG" ]; then
echo "Error: CHANGELOG.md not found"
exit 1
fi
NORMALIZED_CHANGELOG="$(mktemp)"
perl -pe 's/\x{FF08}/(/g; s/\x{FF09}/)/g; s/\r$//' "$CHANGELOG" > "$NORMALIZED_CHANGELOG"
HEADER_REGEX="^##[[:space:]]+v${VERSION}[[:space:]]*\\([0-9]{4}-[0-9]{2}-[0-9]{2}\\)[[:space:]]*$"
if ! grep -Eq "$HEADER_REGEX" "$NORMALIZED_CHANGELOG"; then
echo "Error: Changelog entry not found for v${VERSION}"
exit 1
fi
awk -v ver="v${VERSION}" '
$0 ~ "^##[[:space:]]+" ver "[[:space:]]*\\(" { in_section = 1 }
in_section && $0 ~ "^##[[:space:]]+" && $0 !~ "^##[[:space:]]+" ver "[[:space:]]*\\(" { exit }
in_section { print }
' "$NORMALIZED_CHANGELOG" > release_notes.md
if [ ! -s release_notes.md ]; then
echo "Error: Release notes are empty for v${VERSION}"
exit 1
fi
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release assets
run: |
mkdir -p release_assets
# Process Windows x64 artifacts
if [ -d "artifacts/deepchat-win-x64" ]; then
cp artifacts/deepchat-win-x64/*.exe release_assets/ 2>/dev/null || true
cp artifacts/deepchat-win-x64/*.msi release_assets/ 2>/dev/null || true
cp artifacts/deepchat-win-x64/*.zip release_assets/ 2>/dev/null || true
cp artifacts/deepchat-win-x64/*.yml release_assets/ 2>/dev/null || true
cp artifacts/deepchat-win-x64/*.blockmap release_assets/ 2>/dev/null || true
fi
# Process Linux x64 artifacts
if [ -d "artifacts/deepchat-linux-x64" ]; then
cp artifacts/deepchat-linux-x64/*.AppImage release_assets/ 2>/dev/null || true
cp artifacts/deepchat-linux-x64/*.deb release_assets/ 2>/dev/null || true
cp artifacts/deepchat-linux-x64/*.rpm release_assets/ 2>/dev/null || true
cp artifacts/deepchat-linux-x64/*.tar.gz release_assets/ 2>/dev/null || true
cp artifacts/deepchat-linux-x64/*.yml release_assets/ 2>/dev/null || true
cp artifacts/deepchat-linux-x64/*.blockmap release_assets/ 2>/dev/null || true
fi
# Process Mac x64 artifacts
if [ -d "artifacts/deepchat-mac-x64" ]; then
cp artifacts/deepchat-mac-x64/*.dmg release_assets/ 2>/dev/null || true
cp artifacts/deepchat-mac-x64/*.zip release_assets/ 2>/dev/null || true
cp artifacts/deepchat-mac-x64/*.blockmap release_assets/ 2>/dev/null || true
fi
# Process Mac arm64 artifacts
if [ -d "artifacts/deepchat-mac-arm64" ]; then
cp artifacts/deepchat-mac-arm64/*.dmg release_assets/ 2>/dev/null || true
cp artifacts/deepchat-mac-arm64/*.zip release_assets/ 2>/dev/null || true
cp artifacts/deepchat-mac-arm64/*.blockmap release_assets/ 2>/dev/null || true
fi
merge_mac_yml() {
local name="$1"
local x64="artifacts/deepchat-mac-x64/$name"
local arm64="artifacts/deepchat-mac-arm64/$name"
if [ -f "$x64" ] && [ -f "$arm64" ]; then
ruby -ryaml -e '
x64 = YAML.load_file(ARGV[0]) || {}
arm = YAML.load_file(ARGV[1]) || {}
merged = x64.dup
merged["version"] ||= arm["version"]
merged["releaseDate"] ||= arm["releaseDate"]
merged["releaseNotes"] ||= arm["releaseNotes"]
merged["path"] ||= arm["path"]
merged["sha512"] ||= arm["sha512"]
files = []
files.concat(x64["files"]) if x64["files"].is_a?(Array)
files.concat(arm["files"]) if arm["files"].is_a?(Array)
merged["files"] = files.uniq { |f| f["url"] }
File.write(ARGV[2], merged.to_yaml)
' "$x64" "$arm64" "release_assets/$name"
elif [ -f "$x64" ]; then
cp "$x64" "release_assets/$name"
elif [ -f "$arm64" ]; then
cp "$arm64" "release_assets/$name"
fi
}
merge_mac_yml latest-mac.yml
merge_mac_yml beta-mac.yml
if [ -z "$(ls -A release_assets)" ]; then
echo "Error: No release assets found"
exit 1
fi
ls -la release_assets/
- name: Create Draft Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.resolve-tag.outputs.tag }}
name: DeepChat V${{ steps.get_version.outputs.version }}
draft: true
prerelease: ${{ steps.get_version.outputs.prerelease == 'true' }}
files: |
release_assets/*
body_path: release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}