1+ # .github/workflows/publish-wheel.yml
2+ # Template workflow for individual NodeTool package repositories
3+ # Copy this to each package repository: nodetool-base, nodetool-huggingface, etc.
4+
5+ name : Build and Publish Wheel
6+
7+ on :
8+ # Trigger on version tags
9+ push :
10+ tags :
11+ - ' v*.*.*'
12+
13+ # Manual trigger for testing
14+ workflow_dispatch :
15+ inputs :
16+ version :
17+ description : ' Version to release (e.g., 0.6.0)'
18+ required : true
19+ type : string
20+ prerelease :
21+ description : ' Mark as prerelease'
22+ required : false
23+ default : false
24+ type : boolean
25+
26+ env :
27+ PACKAGE_NAME : ${{ github.event.repository.name }} # e.g., "nodetool-base"
28+
29+ jobs :
30+ validate :
31+ runs-on : ubuntu-latest
32+ outputs :
33+ version : ${{ steps.get-version.outputs.version }}
34+ tag-name : ${{ steps.get-version.outputs.tag-name }}
35+ is-prerelease : ${{ steps.get-version.outputs.is-prerelease }}
36+ steps :
37+ - name : Get version from tag or input
38+ id : get-version
39+ run : |
40+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
41+ VERSION="${{ github.event.inputs.version }}"
42+ TAG_NAME="v${VERSION}"
43+ IS_PRERELEASE="${{ github.event.inputs.prerelease }}"
44+ else
45+ TAG_NAME="${{ github.ref_name }}"
46+ VERSION="${TAG_NAME#v}"
47+ IS_PRERELEASE="false"
48+ fi
49+
50+ # Validate version format
51+ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([a-zA-Z][0-9]*)?$ ]]; then
52+ echo "❌ Invalid version format: $VERSION"
53+ exit 1
54+ fi
55+
56+ echo "version=$VERSION" >> $GITHUB_OUTPUT
57+ echo "tag-name=$TAG_NAME" >> $GITHUB_OUTPUT
58+ echo "is-prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
59+
60+ echo "📋 Version: $VERSION"
61+ echo "🏷️ Tag: $TAG_NAME"
62+ echo "🔄 Prerelease: $IS_PRERELEASE"
63+
64+ build-wheel :
65+ needs : validate
66+ runs-on : ubuntu-latest
67+ steps :
68+ - name : Checkout code
69+ uses : actions/checkout@v4
70+ with :
71+ ref : ${{ needs.validate.outputs.tag-name }}
72+
73+ - name : Set up Python
74+ uses : actions/setup-python@v4
75+ with :
76+ python-version : ' 3.11'
77+
78+ - name : Install build dependencies
79+ run : |
80+ pip install --upgrade pip
81+ pip install build hatchling twine
82+
83+ - name : Verify version in pyproject.toml
84+ run : |
85+ EXPECTED_VERSION="${{ needs.validate.outputs.version }}"
86+ PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
87+
88+ if [ "$PYPROJECT_VERSION" != "$EXPECTED_VERSION" ]; then
89+ echo "❌ Version mismatch: pyproject.toml has $PYPROJECT_VERSION, expected $EXPECTED_VERSION"
90+ exit 1
91+ fi
92+
93+ echo "✅ Version verified: $PYPROJECT_VERSION"
94+
95+ - name : Build wheel
96+ run : |
97+ echo "🏗️ Building wheel for ${{ env.PACKAGE_NAME }}"
98+ python -m build --wheel
99+
100+ # Verify wheel was created
101+ WHEEL_FILE=$(find dist/ -name "*.whl" -type f)
102+ if [ -z "$WHEEL_FILE" ]; then
103+ echo "❌ No wheel file found in dist/"
104+ exit 1
105+ fi
106+
107+ echo "✅ Built wheel: $(basename $WHEEL_FILE)"
108+ echo "📊 Wheel size: $(du -h $WHEEL_FILE | cut -f1)"
109+
110+ - name : Validate wheel
111+ run : |
112+ WHEEL_FILE=$(find dist/ -name "*.whl" -type f)
113+
114+ # Check wheel contents
115+ echo "🔍 Wheel contents:"
116+ python -m zipfile -l "$WHEEL_FILE" | head -20
117+
118+ # Validate with twine
119+ echo "🔍 Validating wheel with twine:"
120+ twine check "$WHEEL_FILE"
121+
122+ - name : Upload wheel artifact
123+ uses : actions/upload-artifact@v4
124+ with :
125+ name : wheel-${{ env.PACKAGE_NAME }}-${{ needs.validate.outputs.version }}
126+ path : dist/*.whl
127+ retention-days : 30
128+
129+ create-release :
130+ needs : [validate, build-wheel]
131+ runs-on : ubuntu-latest
132+ permissions :
133+ contents : write
134+ steps :
135+ - name : Checkout code
136+ uses : actions/checkout@v4
137+ with :
138+ ref : ${{ needs.validate.outputs.tag-name }}
139+
140+ - name : Download wheel artifact
141+ uses : actions/download-artifact@v4
142+ with :
143+ name : wheel-${{ env.PACKAGE_NAME }}-${{ needs.validate.outputs.version }}
144+ path : dist/
145+
146+ - name : Generate release notes
147+ id : release-notes
148+ run : |
149+ VERSION="${{ needs.validate.outputs.version }}"
150+ PACKAGE="${{ env.PACKAGE_NAME }}"
151+
152+ # Generate automatic release notes
153+ cat > release_notes.md << EOF
154+ ## $PACKAGE v$VERSION
155+
156+ ### 📦 Package Information
157+ - **Package**: \`$PACKAGE\`
158+ - **Version**: \`$VERSION\`
159+ - **Python**: \`>=3.11\`
160+
161+ ### 📥 Installation
162+ \`\`\`bash
163+ # From NodeTool registry
164+ pip install --index-url https://nodetool-ai.github.io/nodetool-registry/simple/ $PACKAGE
165+
166+ # Direct from release
167+ pip install https://github.com/${{ github.repository }}/releases/download/${{ needs.validate.outputs.tag-name }}/$PACKAGE-$VERSION-py3-none-any.whl
168+ \`\`\`
169+
170+ ### 🔗 Dependencies
171+ - \`nodetool-core>=0.6.0,<0.7.0\`
172+
173+ ---
174+ *This release was automatically generated from tag \`${{ needs.validate.outputs.tag-name }}\`*
175+ EOF
176+
177+ echo "📄 Generated release notes"
178+
179+ - name : Create GitHub Release
180+ uses : softprops/action-gh-release@v1
181+ with :
182+ tag_name : ${{ needs.validate.outputs.tag-name }}
183+ name : " ${{ env.PACKAGE_NAME }} v${{ needs.validate.outputs.version }}"
184+ body_path : release_notes.md
185+ files : dist/*.whl
186+ draft : false
187+ prerelease : ${{ needs.validate.outputs.is-prerelease }}
188+ generate_release_notes : true
189+ env :
190+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
191+
192+ - name : Notify registry
193+ if : success()
194+ run : |
195+ # Trigger registry update
196+ curl -X POST \
197+ -H "Authorization: token ${{ secrets.REGISTRY_UPDATE_TOKEN || secrets.GITHUB_TOKEN }}" \
198+ -H "Accept: application/vnd.github.v3+json" \
199+ https://api.github.com/repos/nodetool-ai/nodetool-registry/dispatches \
200+ -d '{
201+ "event_type": "package-released",
202+ "client_payload": {
203+ "package": "${{ env.PACKAGE_NAME }}",
204+ "version": "${{ needs.validate.outputs.version }}",
205+ "tag": "${{ needs.validate.outputs.tag-name }}",
206+ "repository": "${{ github.repository }}",
207+ "release_url": "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.tag-name }}"
208+ }
209+ }' || echo "⚠️ Failed to notify registry (non-fatal)"
210+
211+ - name : Create success summary
212+ run : |
213+ echo "## ✅ Release Published Successfully" >> $GITHUB_STEP_SUMMARY
214+ echo "" >> $GITHUB_STEP_SUMMARY
215+ echo "**Package**: \`${{ env.PACKAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
216+ echo "**Version**: \`${{ needs.validate.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
217+ echo "**Tag**: \`${{ needs.validate.outputs.tag-name }}\`" >> $GITHUB_STEP_SUMMARY
218+ echo "" >> $GITHUB_STEP_SUMMARY
219+ echo "**Release URL**: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.tag-name }}" >> $GITHUB_STEP_SUMMARY
220+ echo "" >> $GITHUB_STEP_SUMMARY
221+ echo "### 📦 Installation" >> $GITHUB_STEP_SUMMARY
222+ echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
223+ echo "pip install --index-url https://nodetool-ai.github.io/nodetool-registry/simple/ ${{ env.PACKAGE_NAME }}" >> $GITHUB_STEP_SUMMARY
224+ echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
0 commit comments