Skip to content

Commit 6a6a758

Browse files
improve
0 parents  commit 6a6a758

23 files changed

+4195
-0
lines changed
Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
name: Release PyElevate
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
release_type:
7+
description: 'Release type'
8+
required: true
9+
default: 'patch'
10+
type: choice
11+
options:
12+
- patch
13+
- minor
14+
- major
15+
16+
env:
17+
CARGO_TERM_COLOR: always
18+
RUST_BACKTRACE: 1
19+
20+
jobs:
21+
build:
22+
name: Build ${{ matrix.target }}
23+
runs-on: ${{ matrix.os }}
24+
strategy:
25+
matrix:
26+
include:
27+
- os: ubuntu-latest
28+
target: x86_64-unknown-linux-gnu
29+
artifact_name: pyelevate
30+
asset_name: pyelevate-linux-x86_64
31+
strip: true
32+
33+
- os: ubuntu-latest
34+
target: x86_64-unknown-linux-musl
35+
artifact_name: pyelevate
36+
asset_name: pyelevate-linux-musl-x86_64
37+
strip: true
38+
39+
- os: macos-latest
40+
target: x86_64-apple-darwin
41+
artifact_name: pyelevate
42+
asset_name: pyelevate-macos-x86_64
43+
strip: true
44+
45+
- os: macos-latest
46+
target: aarch64-apple-darwin
47+
artifact_name: pyelevate
48+
asset_name: pyelevate-macos-aarch64
49+
strip: true
50+
51+
- os: windows-latest
52+
target: x86_64-pc-windows-msvc
53+
artifact_name: pyelevate.exe
54+
asset_name: pyelevate-windows-x86_64
55+
strip: false
56+
57+
steps:
58+
- name: Checkout repository
59+
uses: actions/checkout@v4
60+
with:
61+
fetch-depth: 0
62+
63+
- name: Install Rust toolchain
64+
uses: dtolnay/rust-toolchain@stable
65+
with:
66+
targets: ${{ matrix.target }}
67+
68+
- name: Setup Rust cache
69+
uses: Swatinem/rust-cache@v2
70+
with:
71+
workspaces: '.'
72+
cache-on-failure: true
73+
cache-all-crates: true
74+
75+
- name: Install musl-tools (Linux musl only)
76+
if: matrix.target == 'x86_64-unknown-linux-musl'
77+
run: sudo apt-get update && sudo apt-get install -y musl-tools
78+
79+
- name: Build release binary
80+
run: |
81+
cargo build --release --target ${{ matrix.target }}
82+
83+
- name: Strip binary (Unix-like only)
84+
if: matrix.strip
85+
run: |
86+
strip target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
87+
88+
- name: Prepare artifact directory
89+
run: |
90+
mkdir -p artifacts
91+
cp target/${{ matrix.target }}/release/${{ matrix.artifact_name }} artifacts/
92+
93+
- name: Create archive (Unix)
94+
if: runner.os != 'Windows'
95+
run: |
96+
cd artifacts
97+
tar czf ../pyelevate-${{ matrix.asset_name }}.tar.gz *
98+
cd ..
99+
100+
- name: Create archive (Windows)
101+
if: runner.os == 'Windows'
102+
shell: pwsh
103+
run: |
104+
cd artifacts
105+
Compress-Archive -Path * -DestinationPath ..\pyelevate-${{ matrix.asset_name }}.zip
106+
cd ..
107+
108+
- name: Calculate checksums
109+
id: checksums
110+
run: |
111+
if [ "${{ runner.os }}" = "Windows" ]; then
112+
echo "archive=pyelevate-${{ matrix.asset_name }}.zip" >> $GITHUB_OUTPUT
113+
certutil -hashfile pyelevate-${{ matrix.asset_name }}.zip SHA256 | head -1 > checksum.txt
114+
else
115+
echo "archive=pyelevate-${{ matrix.asset_name }}.tar.gz" >> $GITHUB_OUTPUT
116+
sha256sum pyelevate-${{ matrix.asset_name }}.tar.gz > checksum.txt
117+
fi
118+
shell: bash
119+
120+
- name: Upload artifact
121+
uses: actions/upload-artifact@v4
122+
with:
123+
name: build-artifacts-${{ matrix.asset_name }}
124+
path: |
125+
pyelevate-${{ matrix.asset_name }}.*
126+
checksum.txt
127+
retention-days: 1
128+
129+
generate-changelog:
130+
name: Generate Changelog
131+
runs-on: ubuntu-latest
132+
outputs:
133+
changelog: ${{ steps.changelog.outputs.content }}
134+
version: ${{ steps.version.outputs.version }}
135+
steps:
136+
- name: Checkout repository
137+
uses: actions/checkout@v4
138+
with:
139+
fetch-depth: 0
140+
141+
- name: Generate changelog from commits
142+
id: changelog
143+
run: |
144+
{
145+
echo 'content<<EOF'
146+
147+
# Release Notes
148+
149+
**Release Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')**
150+
151+
## Changes in this release
152+
153+
### Features
154+
git log --oneline --grep='feat' HEAD...HEAD~20 2>/dev/null | sed 's/^/- /' || echo "- No new features in this release"
155+
156+
### Bug Fixes
157+
git log --oneline --grep='fix' HEAD...HEAD~20 2>/dev/null | sed 's/^/- /' || echo "- No bug fixes in this release"
158+
159+
### Documentation
160+
git log --oneline --grep='docs' HEAD...HEAD~20 2>/dev/null | sed 's/^/- /' || echo "- No documentation updates in this release"
161+
162+
### Other Changes
163+
git log --oneline HEAD~0..HEAD~19 2>/dev/null | head -10 | sed 's/^/- /' || echo "- See commit history for details"
164+
165+
## All Commits
166+
167+
EOF
168+
git log --pretty=format:"- %h: %s (%an, %ai)" HEAD~0..HEAD~49 >> changelog.txt 2>/dev/null || echo "See commit history for details" >> changelog.txt
169+
cat changelog.txt >> $GITHUB_OUTPUT
170+
echo 'EOF' >> $GITHUB_OUTPUT
171+
}
172+
shell: bash
173+
174+
- name: Get version
175+
id: version
176+
run: |
177+
VERSION=$(grep '^version' Cargo.toml | head -1 | grep -oP '(?<=")\d+\.\d+\.\d+(?=")')
178+
echo "version=$VERSION" >> $GITHUB_OUTPUT
179+
180+
create-release:
181+
name: Create Release
182+
needs: [build, generate-changelog]
183+
runs-on: ubuntu-latest
184+
permissions:
185+
contents: write
186+
187+
steps:
188+
- name: Checkout repository
189+
uses: actions/checkout@v4
190+
with:
191+
fetch-depth: 0
192+
193+
- name: Download all artifacts
194+
uses: actions/download-artifact@v4
195+
with:
196+
path: release-artifacts
197+
198+
- name: Organize artifacts
199+
run: |
200+
mkdir -p release-files
201+
find release-artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "checksum.txt" \) -exec cp {} release-files/ \;
202+
ls -lah release-files/
203+
204+
- name: Generate checksums manifest
205+
run: |
206+
{
207+
echo "# PyElevate Release Checksums (SHA256)"
208+
echo ""
209+
echo "**Release Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
210+
echo ""
211+
echo "## Binary Checksums"
212+
echo ""
213+
214+
for file in release-files/*.tar.gz release-files/*.zip; do
215+
if [ -f "$file" ]; then
216+
filename=$(basename "$file")
217+
if [[ "$file" == *.tar.gz ]]; then
218+
checksum=$(sha256sum "$file" | awk '{print $1}')
219+
else
220+
checksum=$(sha256sum "$file" | awk '{print $1}')
221+
fi
222+
echo "### $filename"
223+
echo "\`\`\`"
224+
echo "$checksum"
225+
echo "\`\`\`"
226+
echo ""
227+
fi
228+
done
229+
} > release-files/CHECKSUMS.md
230+
231+
cat release-files/CHECKSUMS.md
232+
233+
- name: Create release body
234+
id: release_body
235+
run: |
236+
{
237+
echo 'body<<EOF'
238+
echo "# PyElevate v${{ needs.generate-changelog.outputs.version }}"
239+
echo ""
240+
echo "**Released:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
241+
echo ""
242+
echo "## Release Summary"
243+
echo ""
244+
echo "Modern Python requirements.txt dependency upgrader with interactive TUI"
245+
echo ""
246+
echo "## Download"
247+
echo ""
248+
echo "| Platform | Binary | Checksum |"
249+
echo "|----------|--------|----------|"
250+
251+
for file in release-files/*.tar.gz release-files/*.zip; do
252+
if [ -f "$file" ]; then
253+
filename=$(basename "$file")
254+
checksum=$(sha256sum "$file" | awk '{print $1}')
255+
echo "| $(echo $filename | sed 's/pyelevate-//g' | sed 's/-/ /g' | sed 's/.tar.gz//g' | sed 's/.zip//g') | \`$filename\` | \`${checksum:0:16}...\` |"
256+
fi
257+
done
258+
259+
echo ""
260+
echo "## What's New"
261+
echo ""
262+
echo "See [CHANGELOG.md](CHANGELOG.md) for detailed changes."
263+
echo ""
264+
echo "## Installation"
265+
echo ""
266+
echo "### Linux"
267+
echo "\`\`\`bash"
268+
echo "tar xzf pyelevate-linux-x86_64.tar.gz"
269+
echo "chmod +x pyelevate"
270+
echo "./pyelevate"
271+
echo "\`\`\`"
272+
echo ""
273+
echo "### macOS"
274+
echo "\`\`\`bash"
275+
echo "tar xzf pyelevate-macos-x86_64.tar.gz"
276+
echo "chmod +x pyelevate"
277+
echo "./pyelevate"
278+
echo "\`\`\`"
279+
echo ""
280+
echo "### Windows"
281+
echo "\`\`\`powershell"
282+
echo "Expand-Archive pyelevate-windows-x86_64.zip"
283+
echo ".\pyelevate\pyelevate.exe"
284+
echo "\`\`\`"
285+
echo ""
286+
echo "## Features"
287+
echo ""
288+
echo "- 🎨 Interactive TUI with Ratatui"
289+
echo "- πŸš€ Async PyPI fetching with Tokio"
290+
echo "- πŸ“Š Smart version categorization"
291+
echo "- πŸ” Fuzzy search and filtering"
292+
echo "- β™Ώ Full keyboard navigation"
293+
echo "- πŸ›‘οΈ Safe upgrades with automatic backups"
294+
echo "- πŸ”’ Lock file support"
295+
echo "- πŸ§ͺ Dry-run mode"
296+
echo ""
297+
echo "## Verification"
298+
echo ""
299+
echo "All binaries are signed with SHA256 checksums. See CHECKSUMS.md for details."
300+
echo ""
301+
echo "## System Requirements"
302+
echo ""
303+
echo "- Linux: glibc or musl"
304+
echo "- macOS: 10.13+ (Intel or Apple Silicon)"
305+
echo "- Windows: Windows 7+"
306+
echo ""
307+
echo "EOF
308+
} >> $GITHUB_OUTPUT
309+
310+
- name: Create GitHub Release
311+
uses: softprops/action-gh-release@v1
312+
with:
313+
tag_name: v${{ needs.generate-changelog.outputs.version }}
314+
name: PyElevate v${{ needs.generate-changelog.outputs.version }}
315+
body: ${{ steps.release_body.outputs.body }}
316+
draft: false
317+
prerelease: false
318+
files: release-files/*.tar.gz
319+
token: ${{ secrets.GITHUB_TOKEN }}
320+
321+
- name: Upload Windows ZIP as separate release asset
322+
uses: actions/github-script@v7
323+
with:
324+
script: |
325+
const fs = require('fs');
326+
const path = require('path');
327+
328+
const releaseId = context.payload.release.id;
329+
const zipFiles = fs.readdirSync('release-files').filter(f => f.endsWith('.zip'));
330+
331+
for (const file of zipFiles) {
332+
const filePath = path.join('release-files', file);
333+
const fileContent = fs.readFileSync(filePath);
334+
335+
await github.rest.repos.uploadReleaseAsset({
336+
owner: context.repo.owner,
337+
repo: context.repo.repo,
338+
release_id: releaseId,
339+
name: file,
340+
data: fileContent,
341+
headers: {
342+
'content-length': fileContent.length,
343+
'content-type': 'application/zip'
344+
}
345+
});
346+
347+
console.log(`Uploaded ${file}`);
348+
}
349+
350+
- name: Upload checksums manifest
351+
uses: actions/github-script@v7
352+
with:
353+
script: |
354+
const fs = require('fs');
355+
const releaseId = context.payload.release.id;
356+
const fileContent = fs.readFileSync('release-files/CHECKSUMS.md');
357+
358+
await github.rest.repos.uploadReleaseAsset({
359+
owner: context.repo.owner,
360+
repo: context.repo.repo,
361+
release_id: releaseId,
362+
name: 'CHECKSUMS.md',
363+
data: fileContent,
364+
headers: {
365+
'content-length': fileContent.length,
366+
'content-type': 'text/markdown'
367+
}
368+
});
369+
370+
console.log('Uploaded CHECKSUMS.md');
371+
372+
finalize:
373+
name: Finalize Release
374+
needs: [create-release, generate-changelog]
375+
runs-on: ubuntu-latest
376+
if: success()
377+
378+
steps:
379+
- name: Release Summary
380+
run: |
381+
echo "βœ… Release Complete!"
382+
echo ""
383+
echo "πŸ“¦ PyElevate v${{ needs.generate-changelog.outputs.version }} has been released"
384+
echo "⏰ Release time: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
385+
echo ""
386+
echo "🎯 Available downloads:"
387+
echo " - Linux x86_64 (glibc)"
388+
echo " - Linux x86_64 (musl)"
389+
echo " - macOS x86_64"
390+
echo " - macOS aarch64 (Apple Silicon)"
391+
echo " - Windows x86_64"
392+
echo ""
393+
echo "πŸ” All binaries include SHA256 checksums"
394+
echo "πŸ“ See CHECKSUMS.md for verification"

0 commit comments

Comments
Β (0)