Skip to content

Release

Release #86

Workflow file for this run

name: Release
on:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
workflow_dispatch:
permissions:
contents: write
jobs:
# --- publish-cargo job remains the same ---
publish-cargo:
name: Publish to Cargo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
# --- publish-binaries job: Refactored Python Steps ---
publish-binaries:
name: Publish binaries
runs-on: ${{ matrix.build.OS }}
needs: publish-cargo
strategy:
fail-fast: false
matrix:
build:
- { NAME: linux-x64-glibc, OS: ubuntu-22.04, TOOLCHAIN: stable, TARGET: x86_64-unknown-linux-gnu, NPM_PUBLISH: true, PYPI_PUBLISH: true }
- { NAME: linux-x64-musl, OS: ubuntu-22.04, TOOLCHAIN: stable, TARGET: x86_64-unknown-linux-musl, NPM_PUBLISH: false, PYPI_PUBLISH: true }
- { NAME: linux-x86-glibc, OS: ubuntu-22.04, TOOLCHAIN: stable, TARGET: i686-unknown-linux-gnu, NPM_PUBLISH: false, PYPI_PUBLISH: false }
- { NAME: linux-x86-musl, OS: ubuntu-22.04, TOOLCHAIN: stable, TARGET: i686-unknown-linux-musl, NPM_PUBLISH: false, PYPI_PUBLISH: true }
- { NAME: linux-arm64-glibc, OS: ubuntu-22.04, TOOLCHAIN: stable, TARGET: aarch64-unknown-linux-gnu, NPM_PUBLISH: true, PYPI_PUBLISH: true }
- { NAME: linux-arm64-musl, OS: ubuntu-22.04, TOOLCHAIN: stable, TARGET: aarch64-unknown-linux-musl, NPM_PUBLISH: false, PYPI_PUBLISH: true }
- { NAME: win32-x64-msvc, OS: windows-latest, TOOLCHAIN: stable, TARGET: x86_64-pc-windows-msvc, NPM_PUBLISH: true, PYPI_PUBLISH: true }
- { NAME: win32-x86-msvc, OS: windows-latest, TOOLCHAIN: stable, TARGET: i686-pc-windows-msvc, NPM_PUBLISH: false, PYPI_PUBLISH: true }
- { NAME: win32-arm64-msvc, OS: windows-latest, TOOLCHAIN: stable, TARGET: aarch64-pc-windows-msvc, NPM_PUBLISH: true, PYPI_PUBLISH: false }
- { NAME: darwin-x64, OS: macos-15, TOOLCHAIN: stable, TARGET: x86_64-apple-darwin, NPM_PUBLISH: true, PYPI_PUBLISH: true }
- { NAME: darwin-arm64, OS: macos-15, TOOLCHAIN: stable, TARGET: aarch64-apple-darwin, NPM_PUBLISH: true, PYPI_PUBLISH: true }
steps:
- uses: actions/checkout@v4
# Ensure pyproject.toml and Rust Cargo.toml are present and correct
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.build.TOOLCHAIN }}
target: ${{ matrix.build.TARGET }}
# Optional: Use 'cross' for more reliable cross-compilation on Linux
- name: Install cross
if: runner.os == 'Linux'
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Set release version
shell: bash
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Build Rust Binary
shell: bash
run: |
# Build the Rust binary for the target platform using cross or cargo
if [[ "${{ runner.os }}" == "Linux" ]]; then
# Use cross for Linux cross-compilation if installed
cross build --release --target ${{ matrix.build.TARGET }}
else
# Use standard cargo build for native compilation (macOS, Windows)
cargo build --release --target ${{ matrix.build.TARGET }}
fi
# Verify the binary was built
BINARY_PATH="target/${{ matrix.build.TARGET }}/release/gitforge" # Adjust if your binary name is different
if [[ "${{ matrix.build.TARGET }}" == *"windows"* ]]; then
BINARY_PATH="${BINARY_PATH}.exe"
fi
if [ ! -f "$BINARY_PATH" ]; then
echo "Error: Binary not found at $BINARY_PATH"
exit 1
fi
echo "Built binary: $BINARY_PATH"
# --- NEW: Python Wheel Build using Maturin ---
- name: Setup Python for Maturin
if: matrix.build.PYPI_PUBLISH == true
uses: actions/setup-python@v5 # Use latest setup-python action
with:
python-version: '3.11' # Use a version compatible with your minimum requirement
- name: Install Maturin
if: matrix.build.PYPI_PUBLISH == true
run: |
python -m pip install --upgrade pip
pip install maturin
- name: Build Python Wheel with Maturin
if: matrix.build.PYPI_PUBLISH == true
shell: bash
run: |
# Run maturin build for the current target platform
# It will use the pyproject.toml in the repo root
# Ensure your pyproject.toml has: bindings = "bin" and module-name = "gitforge" (or correct name)
maturin build --release --target ${{ matrix.build.TARGET }}
# Verify the wheel was created
WHEEL_DIR="target/wheels"
if [ ! -d "$WHEEL_DIR" ] || [ -z "$(ls -A $WHEEL_DIR/*.whl 2>/dev/null)" ]; then
echo "Error: No wheel file found in $WHEEL_DIR"
ls -la $WHEEL_DIR # List directory contents for debugging
exit 1
fi
echo "Built wheel in $WHEEL_DIR:"
ls -la $WHEEL_DIR/*.whl
# --- Upload Wheels as Artifacts for later PyPI publish ---
- name: Upload Python Wheels as Artifacts
if: matrix.build.PYPI_PUBLISH == true
uses: actions/upload-artifact@v4
with:
name: python-wheels-${{ matrix.build.NAME }} # Unique name per platform
path: target/wheels/*.whl
if-no-files-found: error # Fail if no wheels are found
# --- OLD NPM Steps (Assuming they work correctly) ---
# Prepare npm package structure (this part can stay if needed for npm)
- name: Prepare NPM Package Structure (if needed)
if: matrix.build.NPM_PUBLISH == true
shell: bash
run: |
# This step prepares files specifically for the npm package, separate from PyPI wheels
VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
mkdir -p npm/bin
# Copy the built binary to npm/bin (adjust path if different)
BINARY_NAME="gitforge"
if [[ "${{ matrix.build.TARGET }}" == *"windows"* ]]; then
BINARY_NAME="${BINARY_NAME}.exe"
fi
cp target/${{ matrix.build.TARGET }}/release/$BINARY_NAME npm/bin/
# Create npm package.json (adjust as needed for your npm package structure)
cat > npm/package.json << EOF
{
"name": "@rafaeljohn9/gitforge-${{ matrix.build.NAME }}",
"version": "$VERSION",
"bin": {
"gitforge": "./bin/$BINARY_NAME"
},
"files": ["bin/"],
"license": "APACHE-2.0"
}
EOF
echo "Prepared NPM package structure in npm/ directory."
- name: Setup Node.js for NPM
if: matrix.build.NPM_PUBLISH == true
uses: actions/setup-node@v4
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- name: Publish platform-specific NPM package
if: matrix.build.NPM_PUBLISH == true
shell: bash
run: |
cd npm # Enter the npm directory prepared earlier
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# --- OLD GitHub Release Upload (for raw binaries) ---
- name: Prepare Binary for GitHub Release
shell: bash
run: |
# Copy the built binary with a platform-specific name for GitHub Release
BINARY_NAME="gitforge-${{ matrix.build.NAME }}"
if [[ "${{ matrix.build.TARGET }}" == *"windows"* ]]; then
BINARY_NAME="${BINARY_NAME}.exe"
cp target/${{ matrix.build.TARGET }}/release/gitforge.exe $BINARY_NAME
else
cp target/${{ matrix.build.TARGET }}/release/gitforge $BINARY_NAME
chmod +x $BINARY_NAME
fi
echo "BINARY=$BINARY_NAME" >> $GITHUB_ENV
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: ${{ env.BINARY }}
overwrite: true
fail_on_unmatched_files: false
# --- NEW: Single Job to Publish ALL Wheels to PyPI ---
publish-python-wheels-to-pypi:
name: Publish Python Wheels to PyPI
needs: publish-binaries # Wait for all publish-binaries jobs to finish and upload artifacts
runs-on: ubuntu-latest
environment: pypi # Use a GitHub environment for PyPI secrets
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
steps:
- name: Download all Python wheel artifacts
uses: actions/download-artifact@v4
with:
pattern: python-wheels-* # Download all artifacts matching the pattern
path: dist # Download to a 'dist' directory
merge-multiple: true # Combine artifacts from different matrix runs into one 'dist' folder
- name: Publish all wheels to PyPI
# Uses the PYPI_API_TOKEN secret configured in the GitHub environment
# Recommended: Use trusted publishing (OIDC) for security
uses: pypa/gh-action-pypi-publish@release/v1
# Ensure you have configured trusted publishing on PyPI for your project
# --- publish-npm-unified job remains the same ---
publish-npm-unified:
name: Publish unified NPM package
runs-on: ubuntu-latest
needs: publish-binaries
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- name: Set release version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Create unified package
run: |
mkdir -p gitforge-unified/bin
# Create package.json
cat > gitforge-unified/package.json << EOF
{
"name": "gitforge",
"version": "${{ env.RELEASE_VERSION }}",
"description": "GitHub Templates CLI tool (platform-aware wrapper)",
"bin": {
"gitforge": "./bin/gitforge"
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/${{ github.repository }}.git"
},
"optionalDependencies": {
"gitforge-linux-x64": "${{ env.RELEASE_VERSION }}",
"gitforge-linux-arm64": "${{ env.RELEASE_VERSION }}",
"gitforge-darwin-x64": "${{ env.RELEASE_VERSION }}",
"gitforge-darwin-arm64": "${{ env.RELEASE_VERSION }}",
"gitforge-windows-x64": "${{ env.RELEASE_VERSION }}",
"gitforge-windows-arm64": "${{ env.RELEASE_VERSION }}"
},
"files": ["bin/"],
"engines": {
"node": ">=14"
}
}
EOF
# Create the smart wrapper
cat > gitforge-unified/bin/gitforge << 'EOF'
#!/usr/bin/env node
const { execFileSync } = require('child_process');
const path = require('path');
const os = require('os');
const platform = os.platform();
const arch = os.arch();
let packageName;
if (platform === 'win32') {
if (arch === 'arm64') {
packageName = 'gitforge-windows-arm64';
} else {
packageName = 'gitforge-windows-x64';
}
} else if (platform === 'darwin') {
packageName = arch === 'arm64' ? 'gitforge-darwin-arm64' : 'gitforge-darwin-x64';
} else if (platform === 'linux') {
packageName = arch === 'arm64' ? 'gitforge-linux-arm64' : 'gitforge-linux-x64';
} else {
console.error(`Unsupported platform: ${platform}-${arch}`);
process.exit(1);
}
try {
const pkgPath = require.resolve(`${packageName}/package.json`);
const binPath = path.join(path.dirname(pkgPath), 'bin', platform === 'win32' ? 'gitforge.exe' : 'gitforge');
execFileSync(binPath, process.argv.slice(2), { stdio: 'inherit' });
} catch (err) {
console.error(`Platform-specific package not found: ${packageName}`);
console.error(`Install it directly:`);
console.error(` npm install -g ${packageName}`);
console.error(` OR`);
console.error(` npm install -g gitforge-${platform}-${arch}`);
process.exit(1);
}
EOF
chmod +x gitforge-unified/bin/gitforge
cd gitforge-unified
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# --- publish-homebrew job remains the same ---
publish-homebrew:
name: Update Homebrew formula
runs-on: macos-latest
needs: publish-binaries
steps:
- name: Set release version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Update Homebrew formula
run: |
VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
# Get release assets
LINUX_URL="https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/gitforge-linux-x64-glibc"
DARWIN_ARM_URL="https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/gitforge-darwin-arm64"
DARWIN_X64_URL="https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/gitforge-darwin-x64"
# Calculate checksums
wget -q $LINUX_URL -O linux-binary
wget -q $DARWIN_ARM_URL -O darwin-arm-binary
wget -q $DARWIN_X64_URL -O darwin-x64-binary
LINUX_SHA=$(shasum -a 256 linux-binary | cut -d' ' -f1)
DARWIN_ARM_SHA=$(shasum -a 256 darwin-arm-binary | cut -d' ' -f1)
DARWIN_X64_SHA=$(shasum -a 256 darwin-x64-binary | cut -d' ' -f1)
# Clone homebrew tap
git clone https://github.com/rafaeljohn9/homebrew-tap.git
cd homebrew-tap
# Create/update formula
cat > Formula/gitforge.rb << EOF
class GhTemplates < Formula
desc "GitHub Templates CLI tool"
homepage "https://github.com/${{ github.repository }}"
version "$VERSION"
on_macos do
if Hardware::CPU.arm?
url "$DARWIN_ARM_URL"
sha256 "$DARWIN_ARM_SHA"
else
url "$DARWIN_X64_URL"
sha256 "$DARWIN_X64_SHA"
end
end
on_linux do
url "$LINUX_URL"
sha256 "$LINUX_SHA"
end
def install
bin.install "gitforge"
end
test do
system "#{bin}/gitforge", "--version"
end
end
EOF
# Commit and push
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/gitforge.rb
git commit -m "Update gitforge to $VERSION"
git push https://x-access-token:${{ secrets.HOMEBREW_GITHUB_TOKEN }}@github.com/rafaeljohn9/homebrew-tap.git