Skip to content

Create Release

Create Release #5

# .github/workflows/create-release.yml
# Manual workflow to create GitHub releases from stored build artifacts.
# This allows you to test builds locally before making them public.
name: Create Release
on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag version to release (e.g., v1.0.0)'
required: true
type: string
release_type:
description: 'Release type'
required: true
default: 'release'
type: choice
options:
- release
- prerelease
make_latest:
description: 'Mark as latest release'
required: true
default: true
type: boolean
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write # To create the release
actions: read # To download artifacts
pull-requests: read # To read PRs for the changelog
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.tag_name }}
- name: Validate tag exists
run: |
if ! git tag --list | grep -q "^${{ inputs.tag_name }}$"; then
echo "ERROR: Tag '${{ inputs.tag_name }}' does not exist!"
exit 1
fi
echo "SUCCESS: Tag '${{ inputs.tag_name }}' found"
- name: Find build artifact
id: find_artifact
uses: actions/github-script@v7
with:
script: |
const artifactName = `PyFlowGraph-Windows-${{ inputs.tag_name }}`;
// Search for recent successful builds (last 50 runs)
const workflowRuns = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'windows-build.yml',
status: 'completed',
conclusion: 'success',
per_page: 50
});
console.log(`Searching for artifact '${artifactName}' in ${workflowRuns.data.workflow_runs.length} recent successful builds...`);
// Search through recent successful builds to find the artifact
let foundArtifact = null;
let foundRunId = null;
for (const run of workflowRuns.data.workflow_runs) {
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run.id
});
const artifact = artifacts.data.artifacts.find(a => a.name === artifactName);
if (artifact) {
foundArtifact = artifact;
foundRunId = run.id;
console.log(`Found artifact '${artifactName}' in build run ${run.id} (commit: ${run.head_sha.substring(0,7)})`);
break;
}
}
if (!foundArtifact) {
throw new Error(`Artifact '${artifactName}' not found in any recent successful builds`);
}
console.log(`Using artifact: ${foundArtifact.name} (ID: ${foundArtifact.id})`);
core.setOutput('artifact_id', foundArtifact.id);
core.setOutput('run_id', foundRunId);
core.setOutput('artifact_name', foundArtifact.name);
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: PyFlowGraph-Windows-${{ inputs.tag_name }}
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ steps.find_artifact.outputs.run_id }}
- name: Verify and prepare artifact for release
id: verify
run: |
zip_file="PyFlowGraph-Windows-${{ inputs.tag_name }}.zip"
# Check if PyFlowGraph.exe exists (artifact was extracted directly to working directory)
if [ ! -f "PyFlowGraph.exe" ]; then
echo "ERROR: PyFlowGraph.exe not found in current directory"
echo "Directory contents:"
ls -la
exit 1
fi
# Verify key components exist
if [ ! -d "examples" ] || [ ! -d "python_runtime" ]; then
echo "ERROR: Missing required directories (examples or python_runtime)"
ls -la
exit 1
fi
echo "SUCCESS: Found all required artifact components"
echo "- PyFlowGraph.exe: $(ls -lh PyFlowGraph.exe | awk '{print $5}')"
echo "- Examples directory: $(ls examples | wc -l) files"
echo "- Python runtime directory exists: ✅"
# Create release zip file from current directory contents
echo "Creating release zip: $zip_file"
if command -v zip >/dev/null 2>&1; then
# Exclude development files but keep examples/*.md and LICENSE.txt
zip -r "$zip_file" . -x ".git/*" ".github/*" "src/*" "tests/*" "test_reports/*" "docs/*" "images/*" "pre-release/*" ".gitignore" "README.md" "CLAUDE.md" "requirements.txt" "run.sh" "run.bat" "run_test_gui.bat" "*.log"
else
# Fallback for systems without zip command
tar -czf "${zip_file%.zip}.tar.gz" --exclude='.git' --exclude='.github' --exclude='src' --exclude='tests' --exclude='test_reports' --exclude='docs' --exclude='images' --exclude='pre-release' --exclude='.gitignore' --exclude='README.md' --exclude='CLAUDE.md' --exclude='requirements.txt' --exclude='run.sh' --exclude='run.bat' --exclude='run_test_gui.bat' --exclude='*.log' .
zip_file="${zip_file%.zip}.tar.gz"
fi
file_size=$(stat -f%z "$zip_file" 2>/dev/null || stat -c%s "$zip_file" 2>/dev/null)
echo "SUCCESS: Created release package: $zip_file ($file_size bytes)"
echo "zip_file=$zip_file" >> $GITHUB_OUTPUT
- name: Generate changelog
id: changelog
uses: mikepenz/release-changelog-builder-action@v4
with:
fromTag: ${{ inputs.tag_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ inputs.tag_name }}
name: ${{ inputs.tag_name }}
body: |
## What's Changed
---
${{ steps.changelog.outputs.changelog }}
---
**Build Information:**
- Built from commit: ${{ github.sha }}
- Artifact: ${{ steps.verify.outputs.zip_file }}
- Build run: [#${{ steps.find_artifact.outputs.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ steps.find_artifact.outputs.run_id }})
files: ${{ steps.verify.outputs.zip_file }}
prerelease: ${{ inputs.release_type == 'prerelease' }}
make_latest: ${{ inputs.make_latest }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release summary
run: |
echo "## Release Created Successfully" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Tag:** ${{ inputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "**Type:** ${{ inputs.release_type }}" >> $GITHUB_STEP_SUMMARY
echo "**Latest:** ${{ inputs.make_latest }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Release URL:** ${{ steps.create_release.outputs.url }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The release has been published and is now available for download."