44 push :
55 tags :
66 - ' v*'
7- release :
8- types : [created]
97
108permissions :
119 contents : write
@@ -15,112 +13,276 @@ jobs:
1513 runs-on : ${{ matrix.os }}
1614 strategy :
1715 matrix :
18- os : [ubuntu-latest, windows-latest]
19- python-version : ['3.13']
16+ include :
17+ - os : windows-latest
18+ target : x86_64-pc-windows-msvc
19+ sidecar-ext : .exe
20+ - os : ubuntu-22.04
21+ target : x86_64-unknown-linux-gnu
22+ sidecar-ext : ' '
2023
2124 steps :
2225 - uses : actions/checkout@v4
2326
2427 - name : Set up Python
25- uses : actions/setup-python@v4
28+ uses : actions/setup-python@v5
2629 with :
27- python-version : ${{ matrix.python-version }}
30+ python-version : ' 3.13 '
2831
2932 - name : Install Node.js
3033 uses : actions/setup-node@v4
3134 with :
32- node-version : ' 20'
35+ node-version : ' 22'
36+
37+ - name : Install Rust
38+ uses : dtolnay/rust-toolchain@stable
3339
34- - name : Install dependencies (Linux)
35- if : matrix.os == 'ubuntu-latest'
40+ - name : Rust cache
41+ uses : Swatinem/rust-cache@v2
42+ with :
43+ workspaces : frontend/src-tauri
44+
45+ - name : Install system dependencies (Linux)
46+ if : runner.os == 'Linux'
3647 run : |
3748 sudo apt-get update
38- sudo apt-get install -y portaudio19-dev python3-dev
49+ sudo apt-get install -y \
50+ libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev \
51+ patchelf portaudio19-dev
3952
4053 - name : Install Python dependencies
4154 run : |
4255 python -m pip install --upgrade pip
4356 pip install pyinstaller
44- # Install project dependencies directly from pyproject.toml without building the package
4557 python -c "
4658 import tomllib
4759 with open('pyproject.toml', 'rb') as f:
4860 data = tomllib.load(f)
4961 deps = data['project']['dependencies']
50- print('Installing dependencies:', deps)
5162 import subprocess
5263 subprocess.run(['pip', 'install'] + deps, check=True)
5364 "
54- # Install certifi for SSL certificate support
5565 pip install certifi
5666
57- - name : Install frontend dependencies
58- run : |
59- cd frontend
60- npm install
61- cd ..
67+ - name : Build sidecar binary
68+ run : python scripts/build_sidecar.py
6269
63- - name : Build frontend
64- run : |
65- cd frontend
66- npm run build
67- cd ..
70+ - name : Install frontend dependencies
71+ working-directory : frontend
72+ run : npm install
6873
69- - name : Build executable
70- run : pyinstaller echo.spec --clean --noconfirm
74+ - name : Build Tauri app
75+ working-directory : frontend
76+ env :
77+ TAURI_SIGNING_PRIVATE_KEY : ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
78+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD : ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
79+ run : npx tauri build
7180
72- - name : Create portable package (Linux)
73- if : matrix.os == 'ubuntu-latest'
81+ - name : Collect artifacts (Windows)
82+ if : runner.os == 'Windows'
83+ shell : bash
7484 run : |
75- mkdir -p dist/echo-portable
76- cp dist/echo dist/echo-portable/
77- chmod +x dist/echo dist/echo-portable/echo
78- cp README.md dist/echo-portable/ 2>/dev/null || true
85+ mkdir -p artifacts
86+ cp frontend/src-tauri/target/release/bundle/nsis/*.exe artifacts/ || true
87+ cp frontend/src-tauri/target/release/bundle/nsis/*.nsis.zip artifacts/ || true
88+ cp frontend/src-tauri/target/release/bundle/nsis/*.nsis.zip.sig artifacts/ || true
7989
80- - name : Create portable package (Windows )
81- if : matrix .os == 'windows-latest '
90+ - name : Collect artifacts (Linux )
91+ if : runner .os == 'Linux '
8292 run : |
83- mkdir -p dist/echo-portable
84- copy dist\echo.exe dist\echo-portable\
85- copy README.md dist\echo-portable\ 2>nul || echo README.md not found
93+ mkdir -p artifacts
94+ cp frontend/src-tauri/target/release/bundle/appimage/*.AppImage artifacts/ || true
95+ cp frontend/src-tauri/target/release/bundle/appimage/*.AppImage.tar.gz artifacts/ || true
96+ cp frontend/src-tauri/target/release/bundle/appimage/*.AppImage.tar.gz.sig artifacts/ || true
97+ cp frontend/src-tauri/target/release/bundle/deb/*.deb artifacts/ || true
8698
8799 - name : Upload artifacts
88100 uses : actions/upload-artifact@v4
89101 with :
90- name : build-${{ matrix.os }}
91- path : |
92- dist/echo*
93- dist/echo-portable/**
94- retention-days : 1
102+ name : build-${{ matrix.target }}
103+ path : artifacts/*
104+ retention-days : 3
95105
96106 release :
97107 needs : build
98108 runs-on : ubuntu-latest
99- if : always()
100109
101110 steps :
111+ - uses : actions/checkout@v4
112+
102113 - name : Download all artifacts
103114 uses : actions/download-artifact@v4
115+ with :
116+ path : artifacts
117+ merge-multiple : true
118+
119+ - name : List artifacts
120+ run : ls -la artifacts/
104121
105- - name : Clean up existing release (only if triggered by tag)
106- if : github.event_name == 'push'
122+ - name : Get version from tag
123+ id : version
124+ run : echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
125+
126+ - name : Generate latest.json
107127 run : |
108- gh release delete ${{ github.ref_name }} --yes || echo "No existing release to delete"
109- env :
110- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
128+ VERSION="${{ steps.version.outputs.version }}"
129+ TAG="${GITHUB_REF_NAME}"
130+
131+ # Read signatures
132+ WIN_SIG=""
133+ LINUX_SIG=""
134+ if [ -f "artifacts/Echo_${VERSION}_x64-setup.nsis.zip.sig" ]; then
135+ WIN_SIG=$(cat "artifacts/Echo_${VERSION}_x64-setup.nsis.zip.sig")
136+ fi
137+ if ls artifacts/*_amd64.AppImage.tar.gz.sig 1>/dev/null 2>&1; then
138+ LINUX_SIG=$(cat artifacts/*_amd64.AppImage.tar.gz.sig)
139+ fi
140+
141+ # Build platforms JSON
142+ PLATFORMS="{}"
143+ if [ -n "$WIN_SIG" ]; then
144+ PLATFORMS=$(echo "$PLATFORMS" | jq \
145+ --arg sig "$WIN_SIG" \
146+ --arg url "https://gitee.com/KaUpane/echo/releases/download/${TAG}/Echo_${VERSION}_x64-setup.nsis.zip" \
147+ '. + {"windows-x86_64": {"signature": $sig, "url": $url}}')
148+ fi
149+ if [ -n "$LINUX_SIG" ]; then
150+ APPIMAGE_NAME=$(basename artifacts/*_amd64.AppImage.tar.gz .sig 2>/dev/null | head -1)
151+ PLATFORMS=$(echo "$PLATFORMS" | jq \
152+ --arg sig "$LINUX_SIG" \
153+ --arg url "https://gitee.com/KaUpane/echo/releases/download/${TAG}/${APPIMAGE_NAME}" \
154+ '. + {"linux-x86_64": {"signature": $sig, "url": $url}}')
155+ fi
156+
157+ # Write latest.json
158+ jq -n \
159+ --arg version "$VERSION" \
160+ --arg pub_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
161+ --argjson platforms "$PLATFORMS" \
162+ '{version: $version, pub_date: $pub_date, platforms: $platforms}' \
163+ > artifacts/latest.json
164+
165+ echo "Generated latest.json:"
166+ cat artifacts/latest.json
111167
112- - name : Create/Update Release
113- uses : softprops/action-gh-release@v1
168+ - name : Create GitHub Release
169+ uses : softprops/action-gh-release@v2
114170 with :
115- files : |
116- build-ubuntu-latest/*
117- build-windows-latest/*
171+ files : artifacts/*
118172 draft : false
119173 prerelease : false
120- generate_release_notes : ${{ github.event_name == 'push' }} # Only generate notes if triggered by tag push
174+ generate_release_notes : true
121175 tag_name : ${{ github.ref_name }}
122176 name : Echo ${{ github.ref_name }}
123- # If release was created via gh release, preserve its notes and update with binaries
124- append_body : ${{ github.event_name == 'release' }}
125177 env :
126- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
178+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
179+
180+ - name : Publish to Gitee Release
181+ env :
182+ GITEE_TOKEN : ${{ secrets.GITEE_PERSONAL_ACCESS_TOKEN }}
183+ TAG : ${{ github.ref_name }}
184+ VERSION : ${{ steps.version.outputs.version }}
185+ run : |
186+ OWNER="KaUpane"
187+ REPO="echo"
188+
189+ # Delete existing release for this tag (if re-running)
190+ EXISTING=$(curl -s "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases/tags/${TAG}?access_token=${GITEE_TOKEN}")
191+ EXISTING_ID=$(echo "$EXISTING" | jq -r '.id // empty')
192+ if [ -n "$EXISTING_ID" ]; then
193+ echo "Deleting existing Gitee release ${EXISTING_ID}..."
194+ curl -s -X DELETE "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases/${EXISTING_ID}?access_token=${GITEE_TOKEN}"
195+ fi
196+
197+ # Create release
198+ echo "Creating Gitee release for ${TAG}..."
199+ RELEASE_RESPONSE=$(curl -s -X POST "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases" \
200+ -H "Content-Type: application/json" \
201+ -d "{
202+ \"access_token\": \"${GITEE_TOKEN}\",
203+ \"tag_name\": \"${TAG}\",
204+ \"name\": \"Echo ${TAG}\",
205+ \"body\": \"Automated release for ${TAG}. Download the installer for your platform below.\",
206+ \"prerelease\": false
207+ }")
208+
209+ RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
210+ if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
211+ echo "Failed to create Gitee release!"
212+ echo "$RELEASE_RESPONSE"
213+ exit 1
214+ fi
215+ echo "Created Gitee release ID: ${RELEASE_ID}"
216+
217+ # Upload each artifact
218+ for FILE in artifacts/*; do
219+ FILENAME=$(basename "$FILE")
220+ echo "Uploading ${FILENAME} to Gitee..."
221+ curl -s -X POST "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}/attach_files" \
222+ -H "Content-Type: multipart/form-data" \
223+ -F "access_token=${GITEE_TOKEN}" \
224+ -F "file=@${FILE}" || echo "Warning: failed to upload ${FILENAME}"
225+ done
226+
227+ echo "Gitee release published successfully!"
228+
229+ - name : Update Gitee 'latest' release
230+ env :
231+ GITEE_TOKEN : ${{ secrets.GITEE_PERSONAL_ACCESS_TOKEN }}
232+ TAG : ${{ github.ref_name }}
233+ VERSION : ${{ steps.version.outputs.version }}
234+ run : |
235+ OWNER="KaUpane"
236+ REPO="echo"
237+
238+ # Delete existing 'latest' release
239+ EXISTING=$(curl -s "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases/tags/latest?access_token=${GITEE_TOKEN}")
240+ EXISTING_ID=$(echo "$EXISTING" | jq -r '.id // empty')
241+ if [ -n "$EXISTING_ID" ]; then
242+ echo "Deleting existing Gitee 'latest' release..."
243+ curl -s -X DELETE "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases/${EXISTING_ID}?access_token=${GITEE_TOKEN}"
244+ fi
245+
246+ # Delete the 'latest' tag if it exists (via API)
247+ curl -s -X DELETE "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/git/refs/tags/latest?access_token=${GITEE_TOKEN}" || true
248+
249+ # Create 'latest' tag pointing to the same commit
250+ COMMIT_SHA=$(git rev-parse HEAD)
251+ curl -s -X POST "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/git/refs" \
252+ -H "Content-Type: application/json" \
253+ -d "{
254+ \"access_token\": \"${GITEE_TOKEN}\",
255+ \"ref\": \"refs/tags/latest\",
256+ \"sha\": \"${COMMIT_SHA}\"
257+ }" || true
258+
259+ # Create 'latest' release with update artifacts
260+ RELEASE_RESPONSE=$(curl -s -X POST "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases" \
261+ -H "Content-Type: application/json" \
262+ -d "{
263+ \"access_token\": \"${GITEE_TOKEN}\",
264+ \"tag_name\": \"latest\",
265+ \"name\": \"Latest (${TAG})\",
266+ \"body\": \"This release always points to the latest version. Current: ${TAG}\",
267+ \"prerelease\": false
268+ }")
269+
270+ RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
271+ if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
272+ echo "Warning: Failed to create Gitee 'latest' release"
273+ echo "$RELEASE_RESPONSE"
274+ exit 0
275+ fi
276+
277+ # Upload latest.json and update bundles to 'latest' release
278+ for FILE in artifacts/*.nsis.zip artifacts/*.AppImage.tar.gz artifacts/latest.json; do
279+ [ -f "$FILE" ] || continue
280+ FILENAME=$(basename "$FILE")
281+ echo "Uploading ${FILENAME} to Gitee 'latest' release..."
282+ curl -s -X POST "https://gitee.com/api/v5/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}/attach_files" \
283+ -H "Content-Type: multipart/form-data" \
284+ -F "access_token=${GITEE_TOKEN}" \
285+ -F "file=@${FILE}" || echo "Warning: failed to upload ${FILENAME}"
286+ done
287+
288+ echo "Gitee 'latest' release updated successfully!"
0 commit comments