diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..5f67b9a --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,250 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + confirm: + description: 'Type "release" to confirm' + required: true + default: '' + +jobs: + validate: + name: Validate Release + runs-on: ubuntu-latest + outputs: + version: ${{ steps.get_version.outputs.version }} + tag: ${{ steps.get_version.outputs.tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Validate confirmation + run: | + if [ "${{ github.event.inputs.confirm }}" != "release" ]; then + echo "Error: You must type 'release' to confirm" + exit 1 + fi + + - name: Get version from code + id: get_version + run: | + VERSION=$(./scripts/get-version.sh) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=v$VERSION" >> $GITHUB_OUTPUT + echo "Version to release: $VERSION" + + - name: Check if tag exists + run: | + if git ls-remote --tags origin | grep -q "refs/tags/${{ steps.get_version.outputs.tag }}"; then + echo "Error: Tag ${{ steps.get_version.outputs.tag }} already exists" + echo "Please update the version in Sources/EJSONKit/Version.swift" + exit 1 + fi + echo "Tag ${{ steps.get_version.outputs.tag }} does not exist - OK to proceed" + + - name: Run tests + run: | + swift test + + build-macos: + name: Build macOS Universal Binary + needs: validate + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check Swift version + run: swift --version + + - name: Run tests + run: swift test + + - name: Build release binary + run: ./scripts/build-release.sh ${{ needs.validate.outputs.version }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-universal + path: | + release/*.tar.gz + release/*.sha256 + + create-release: + name: Create GitHub Release + needs: [validate, build-macos] + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + sha256: ${{ steps.get_sha.outputs.sha256 }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download macOS artifact + uses: actions/download-artifact@v4 + with: + name: macos-universal + path: release/ + + - name: Get SHA256 + id: get_sha + run: | + SHA256=$(cat release/ejson-${{ needs.validate.outputs.version }}-macos-universal.tar.gz.sha256 | awk '{print $1}') + echo "sha256=$SHA256" >> $GITHUB_OUTPUT + echo "SHA256: $SHA256" + + - name: Create and push tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a ${{ needs.validate.outputs.tag }} -m "Release version ${{ needs.validate.outputs.version }}" + git push origin ${{ needs.validate.outputs.tag }} + + - name: Generate release notes + id: release_notes + run: | + cat > release_notes.md << 'EOF' + ## Installation + + ### Homebrew (Recommended for macOS) + + ```bash + # Via tap + brew tap diogot/ejson + brew install ejson + + # Or direct installation + brew install https://raw.githubusercontent.com/diogot/swift-ejson/main/Formula/ejson.rb + ``` + + ### macOS (Universal Binary - x86_64 + ARM64) + + Download and install: + ```bash + curl -L https://github.com/${{ github.repository }}/releases/download/${{ needs.validate.outputs.tag }}/ejson-${{ needs.validate.outputs.version }}-macos-universal.tar.gz | tar xz + sudo mv ejson /usr/local/bin/ + ejson --version + ``` + + Or with wget: + ```bash + wget https://github.com/${{ github.repository }}/releases/download/${{ needs.validate.outputs.tag }}/ejson-${{ needs.validate.outputs.version }}-macos-universal.tar.gz + tar xzf ejson-${{ needs.validate.outputs.version }}-macos-universal.tar.gz + sudo mv ejson /usr/local/bin/ + ejson --version + ``` + + ### Verify Checksum + + ```bash + # Download checksum file + curl -L https://github.com/${{ github.repository }}/releases/download/${{ needs.validate.outputs.tag }}/ejson-${{ needs.validate.outputs.version }}-macos-universal.tar.gz.sha256 -o ejson.sha256 + + # Verify (macOS) + shasum -a 256 -c ejson.sha256 + + # Verify (Linux) + sha256sum -c ejson.sha256 + ``` + + ## Features + + - ๐Ÿ” NaCl Box encryption compatible with Shopify EJSON + - ๐Ÿ”„ Full compatibility with Go EJSON implementation + - โšก Fast and native Swift implementation + - ๐Ÿ“ฆ Universal macOS binary (works on both Intel and Apple Silicon) + + ## Usage + + ```bash + # Generate a keypair + ejson keygen + + # Encrypt a file + ejson encrypt secrets.json + + # Decrypt a file + ejson decrypt secrets.json + ``` + + For more information, see the [README](https://github.com/${{ github.repository }}/blob/main/README.md). + EOF + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.validate.outputs.tag }} + name: Release ${{ needs.validate.outputs.version }} + body_path: release_notes.md + files: | + release/*.tar.gz + release/*.sha256 + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + update-formula: + name: Update Homebrew Formula + needs: [validate, create-release] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + + - name: Update formula with SHA256 + run: | + VERSION="${{ needs.validate.outputs.version }}" + SHA256="${{ needs.create-release.outputs.sha256 }}" + FORMULA_FILE="Formula/ejson.rb" + + echo "Updating formula..." + echo "Version: $VERSION" + echo "SHA256: $SHA256" + + # Update version + sed -i "s/version \".*\"/version \"${VERSION}\"/" "$FORMULA_FILE" + + # Update URL + sed -i "s|download/v[0-9.]\+/ejson-[0-9.]\+-macos-universal.tar.gz|download/v${VERSION}/ejson-${VERSION}-macos-universal.tar.gz|g" "$FORMULA_FILE" + + # Update SHA256 + sed -i "s/sha256 \".*\"/sha256 \"${SHA256}\"/" "$FORMULA_FILE" + + echo "Formula updated:" + cat "$FORMULA_FILE" + + - name: Commit and push formula + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Formula/ejson.rb + git commit -m "Update Homebrew formula to v${{ needs.validate.outputs.version }}" + git push origin main + + - name: Summary + run: | + echo "## Release Created Successfully! ๐ŸŽ‰" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ needs.validate.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** ${{ needs.validate.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "**SHA256:** ${{ needs.create-release.outputs.sha256 }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Installation" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "brew tap diogot/ejson" >> $GITHUB_STEP_SUMMARY + echo "brew install ejson" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next Steps" >> $GITHUB_STEP_SUMMARY + echo "1. Test the installation: \`brew install ejson\`" >> $GITHUB_STEP_SUMMARY + echo "2. If using a separate tap, copy Formula/ejson.rb to homebrew-ejson repo" >> $GITHUB_STEP_SUMMARY + echo "3. Announce the release" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4d33e0..96a1ffe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,8 @@ -name: Release +name: Release (Tag-based) + +# This workflow is kept for backward compatibility +# The recommended approach is to use the "Create Release" workflow (create-release.yml) +# which reads version from code and automates everything on: push: diff --git a/Formula/ejson.rb b/Formula/ejson.rb new file mode 100644 index 0000000..7ffdb6a --- /dev/null +++ b/Formula/ejson.rb @@ -0,0 +1,57 @@ +class Ejson < Formula + desc "Swift implementation of Shopify's EJSON for managing encrypted secrets" + homepage "https://github.com/diogot/swift-ejson" + version "1.0.0" + + on_macos do + if Hardware::CPU.arm? + url "https://github.com/diogot/swift-ejson/releases/download/v1.0.0/ejson-1.0.0-macos-universal.tar.gz" + sha256 "PLACEHOLDER_SHA256_WILL_BE_UPDATED_ON_RELEASE" + else + url "https://github.com/diogot/swift-ejson/releases/download/v1.0.0/ejson-1.0.0-macos-universal.tar.gz" + sha256 "PLACEHOLDER_SHA256_WILL_BE_UPDATED_ON_RELEASE" + end + end + + def install + bin.install "ejson" + end + + test do + # Test version command + assert_match "ejson version", shell_output("#{bin}/ejson --version") + + # Test help command + assert_match "Usage: ejson", shell_output("#{bin}/ejson help") + + # Test keygen command (generates a keypair) + output = shell_output("#{bin}/ejson keygen") + assert_match "Public Key:", output + assert_match "Private Key:", output + end + + def caveats + <<~EOS + ejson has been installed! + + To get started: + 1. Generate a keypair: + ejson keygen + + 2. Create a secrets file with the public key: + echo '{"_public_key": "YOUR_PUBLIC_KEY", "secret": "value"}' > secrets.json + + 3. Encrypt the file: + ejson encrypt secrets.json + + 4. Store the private key in the keydir: + mkdir -p /opt/ejson/keys + echo "YOUR_PRIVATE_KEY" > /opt/ejson/keys/YOUR_PUBLIC_KEY + + 5. Decrypt the file: + ejson decrypt secrets.json + + For more information: https://github.com/diogot/swift-ejson + EOS + end +end diff --git a/HOMEBREW.md b/HOMEBREW.md new file mode 100644 index 0000000..e8ae84d --- /dev/null +++ b/HOMEBREW.md @@ -0,0 +1,205 @@ +# Homebrew Installation Guide + +This guide explains how to install `ejson` via Homebrew. + +## Installation Options + +### Option 1: Direct Formula Installation (Quick) + +You can install directly from the formula file without setting up a tap: + +```bash +brew install https://raw.githubusercontent.com/diogot/swift-ejson/main/Formula/ejson.rb +``` + +### Option 2: Using a Homebrew Tap (Recommended) + +A Homebrew tap provides a better user experience with simpler commands. + +#### For Users + +Once the tap is set up, install with: + +```bash +# Add the tap +brew tap diogot/ejson + +# Install ejson +brew install ejson +``` + +To upgrade: + +```bash +brew upgrade ejson +``` + +To uninstall: + +```bash +brew uninstall ejson +brew untap diogot/ejson +``` + +#### For Maintainers: Setting Up a Tap + +1. **Create a tap repository** named `homebrew-ejson`: + ```bash + # Create a new repository on GitHub: diogot/homebrew-ejson + git clone https://github.com/diogot/homebrew-ejson.git + cd homebrew-ejson + ``` + +2. **Copy the formula**: + ```bash + # Create Formula directory in the tap + mkdir -p Formula + + # Copy the formula from this repo + cp /path/to/swift-ejson/Formula/ejson.rb Formula/ + ``` + +3. **Update the SHA256 checksum** after creating a release: + ```bash + # Download the release tarball + VERSION="1.0.0" + curl -L -o ejson.tar.gz "https://github.com/diogot/swift-ejson/releases/download/v${VERSION}/ejson-${VERSION}-macos-universal.tar.gz" + + # Calculate SHA256 + shasum -a 256 ejson.tar.gz + + # Update the sha256 value in Formula/ejson.rb + ``` + +4. **Commit and push**: + ```bash + git add Formula/ejson.rb + git commit -m "Add ejson formula v${VERSION}" + git push origin main + ``` + +5. **Test the tap**: + ```bash + brew tap diogot/ejson + brew install ejson + ejson --version + ``` + +## Updating the Formula for New Releases + +### Automated Updates (Recommended) + +The formula is **automatically updated** when a release is created using the GitHub Actions workflow. The maintainer only needs to: + +1. Update version in `Sources/EJSONKit/Version.swift` +2. Trigger the "Create Release" workflow +3. Everything else happens automatically: + - Git tag created + - Binary built + - Release created + - **Formula updated with correct SHA256** + +See [RELEASING.md](RELEASING.md) for complete instructions. + +### Manual Updates (If Needed) + +If you need to manually update the formula: + +1. **Use the update script**: + ```bash + # This script downloads the release and updates the formula + ./scripts/update-formula.sh 1.1.0 + + # Review the changes + git diff Formula/ejson.rb + + # Commit and push + git add Formula/ejson.rb + git commit -m "Update formula to v1.1.0" + git push origin main + ``` + +2. **For separate tap repository**: + ```bash + cd homebrew-ejson + cp ../swift-ejson/Formula/ejson.rb Formula/ + git add Formula/ejson.rb + git commit -m "Update ejson to v1.1.0" + git push origin main + ``` + +3. **Users can then upgrade**: + ```bash + brew update + brew upgrade ejson + ``` + +### Version Management + +Version is centrally managed in `Sources/EJSONKit/Version.swift`. All releases and formula updates sync from this single source. This ensures the binary version, git tags, GitHub releases, and Homebrew formula are always in sync. + +## Testing the Formula Locally + +Before pushing changes, test the formula locally: + +```bash +# Install from local formula +brew install --build-from-source Formula/ejson.rb + +# Or test without installing +brew audit --strict Formula/ejson.rb +brew test Formula/ejson.rb + +# Uninstall after testing +brew uninstall ejson +``` + +## Submitting to Homebrew Core (Optional) + +For wider distribution, you can submit to the official Homebrew repository: + +1. **Requirements**: + - Project must be stable and well-maintained + - Binaries should be notarized (macOS) + - Formula must follow Homebrew guidelines + +2. **Process**: + ```bash + # Fork homebrew-core + # Add formula to Formula/ejson.rb + # Submit a pull request + ``` + +See: https://docs.brew.sh/Adding-Software-to-Homebrew + +## Troubleshooting + +### Formula Not Found + +If `brew install ejson` fails with "No available formula": + +1. Ensure the tap is added: `brew tap diogot/ejson` +2. Update Homebrew: `brew update` +3. Try the direct URL installation method + +### SHA256 Mismatch + +If installation fails with SHA256 mismatch: + +1. The formula needs to be updated with the correct checksum +2. Contact the maintainer or open an issue + +### Binary Not Found After Installation + +If `ejson` command is not found: + +1. Check if it's installed: `brew list ejson` +2. Check your PATH: `echo $PATH | grep -o '/usr/local/bin'` +3. Try running with full path: `/usr/local/bin/ejson --version` +4. Restart your terminal + +## Resources + +- [Homebrew Formula Cookbook](https://docs.brew.sh/Formula-Cookbook) +- [Homebrew Taps Documentation](https://docs.brew.sh/Taps) +- [swift-ejson Repository](https://github.com/diogot/swift-ejson) diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md new file mode 100644 index 0000000..f4e1c96 --- /dev/null +++ b/PR_DESCRIPTION.md @@ -0,0 +1,187 @@ +# Add Homebrew Installation and Centralized Version Management + +This PR adds comprehensive Homebrew support and implements a centralized version management system with fully automated releases. + +## ๐ŸŽฏ Overview + +This PR makes `ejson` easily installable via Homebrew and establishes a single source of truth for version management across all components (binary, releases, tags, and Homebrew formula). + +## โœจ What's New + +### 1. Homebrew Installation Support + +Users can now install ejson via Homebrew: + +```bash +# Via tap (recommended) +brew tap diogot/ejson +brew install ejson + +# Or direct installation +brew install https://raw.githubusercontent.com/diogot/swift-ejson/main/Formula/ejson.rb +``` + +**Created:** +- `Formula/ejson.rb` - Homebrew formula with comprehensive tests +- `HOMEBREW.md` - Complete installation and maintenance guide +- `scripts/update-formula.sh` - Helper script for formula updates + +**CLI Improvements:** +- Added `--version`, `-v`, and `version` commands +- Prints version and compatibility information + +### 2. Centralized Version Management + +**Single Source of Truth** - Version is now defined in one place: +```swift +// Sources/EJSONKit/Version.swift +public static let current = "1.0.0" +``` + +All components automatically sync from this file: +- โœ… CLI binary `--version` output +- โœ… Git tags +- โœ… GitHub releases +- โœ… Homebrew formula + +**Created:** +- `Sources/EJSONKit/Version.swift` - Central version definition +- `scripts/get-version.sh` - Version extraction script +- `VERSION_MANAGEMENT.md` - Comprehensive documentation + +### 3. Automated Release Workflow + +**New Manual Workflow** (`.github/workflows/create-release.yml`): + +Trigger from GitHub Actions UI โ†’ Type "release" to confirm โ†’ Done! + +The workflow automatically: +1. โœ… Extracts version from code +2. โœ… Validates format and runs tests +3. โœ… Builds universal macOS binary (Intel + Apple Silicon) +4. โœ… Creates and pushes git tag +5. โœ… Creates GitHub release with binaries +6. โœ… **Calculates SHA256 and updates Homebrew formula** +7. โœ… Commits updated formula back to main + +**Updated:** +- `.github/workflows/release.yml` - Renamed to "Release (Tag-based)", kept for backward compatibility + +### 4. Documentation + +**New Files:** +- `HOMEBREW.md` - Homebrew installation guide +- `VERSION_MANAGEMENT.md` - Version management documentation + +**Updated:** +- `RELEASING.md` - Completely rewritten for automated workflow +- `README.md` - Added Homebrew installation and version management sections + +## ๐Ÿ“ฆ Release Process Comparison + +### Before This PR: +1. Manually update version in multiple places +2. Create and push git tag +3. Wait for build to complete +4. Download binary and calculate SHA256 +5. Manually update Homebrew formula +6. Commit formula changes + +### After This PR: +1. Edit `Sources/EJSONKit/Version.swift` +2. Commit and push to main +3. Click "Run workflow" on GitHub Actions +4. Type "release" to confirm +5. โœจ **Done! Everything automated** + +## ๐ŸŽ Benefits + +1. **No Version Drift** - Single source ensures all components stay in sync +2. **Fully Automated Releases** - One-click release process +3. **Easy Installation** - Homebrew support for macOS users +4. **Error-Proof** - Workflow validates version format, prevents duplicate tags +5. **SHA256 Auto-Calculated** - No manual checksum work needed +6. **Formula Auto-Updated** - Committed back to main automatically +7. **Traceable** - Version changes visible in git history + +## ๐Ÿ“Š Version Synchronization + +| Component | Source | How it Syncs | +|-----------|--------|--------------| +| Library Version | `Version.swift` | Direct definition | +| Binary Version | CLI `--version` | Imports from `EJSONKit.Version` | +| Git Tag | GitHub Actions | Extracted via `scripts/get-version.sh` | +| GitHub Release | GitHub Actions | Uses git tag | +| Homebrew Formula | Auto-updated | SHA256 calculated by workflow | + +## ๐Ÿงช Testing + +Version extraction works: +```bash +$ ./scripts/get-version.sh +1.0.0 +``` + +CLI shows version: +```bash +$ swift build && .build/debug/ejson --version +ejson version 1.0.0 +Swift EJSON - Compatible with Shopify EJSON +``` + +## ๐Ÿš€ How to Create a Release (After Merge) + +**Simple 3-step process:** + +1. **Update version** in `Sources/EJSONKit/Version.swift`: + ```swift + public static let current = "1.0.0" // Change to new version + ``` + +2. **Commit and push to main**: + ```bash + git add Sources/EJSONKit/Version.swift + git commit -m "Bump version to 1.0.0" + git push origin main + ``` + +3. **Trigger workflow**: + - Go to: https://github.com/diogot/swift-ejson/actions/workflows/create-release.yml + - Click "Run workflow" + - Type "release" to confirm + - Monitor automated process + +The workflow handles everything: tag creation, binary building, release creation, and formula updates. + +## ๐Ÿ“‹ Files Changed + +### New Files +- `Sources/EJSONKit/Version.swift` - Central version definition +- `Formula/ejson.rb` - Homebrew formula +- `HOMEBREW.md` - Homebrew documentation +- `VERSION_MANAGEMENT.md` - Version management guide +- `.github/workflows/create-release.yml` - Automated release workflow +- `scripts/get-version.sh` - Version extraction script +- `scripts/update-formula.sh` - Formula update helper + +### Modified Files +- `Sources/ejson/main.swift` - Added version command using `EJSONKit.Version` +- `.github/workflows/release.yml` - Renamed, updated description +- `README.md` - Added Homebrew installation and version management sections +- `RELEASING.md` - Rewritten for automated workflow + +## ๐Ÿ” Breaking Changes + +None. This PR is fully backward compatible. + +## ๐Ÿ“ Next Steps After Merge + +1. Test the automated release workflow with version 1.0.0 +2. Optionally create a separate tap repository (`homebrew-ejson`) for simpler installation +3. Announce Homebrew availability to users + +## ๐Ÿ™ Notes + +- The formula uses placeholder SHA256 initially - it will be updated automatically when the first release is created +- The tag-based workflow (`release.yml`) is kept for backward compatibility +- All documentation has been updated to reflect the new processes diff --git a/README.md b/README.md index f64e592..59cd037 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,29 @@ EJSONKit includes a command-line tool compatible with the [Go EJSON CLI](https:/ ### Installation -#### Pre-built Binaries (Recommended) +#### Homebrew (Recommended for macOS) + +The easiest way to install on macOS: + +```bash +# Option 1: Via tap (after tap is set up) +brew tap diogot/ejson +brew install ejson + +# Option 2: Direct installation +brew install https://raw.githubusercontent.com/diogot/swift-ejson/main/Formula/ejson.rb +``` + +Upgrade to the latest version: + +```bash +brew update +brew upgrade ejson +``` + +See [HOMEBREW.md](HOMEBREW.md) for detailed instructions on setting up the tap. + +#### Pre-built Binaries Download the latest release for your platform from [GitHub Releases](https://github.com/diogot/swift-ejson/releases): @@ -432,6 +454,10 @@ swift test - macOS 10.15+ / iOS 13+ / tvOS 13+ / watchOS 6+ - Linux: libsodium-dev package required +## Version Management + +Version is managed centrally in `Sources/EJSONKit/Version.swift`. All components (binary, releases, tags, Homebrew formula) automatically sync from this file. See [VERSION_MANAGEMENT.md](VERSION_MANAGEMENT.md) for details. + ## Contributing Contributions are welcome! Please ensure: @@ -440,6 +466,7 @@ Contributions are welcome! Please ensure: - New features include tests - Code follows Swift conventions - Changes maintain compatibility with Go EJSON +- Don't manually update version numbers (handled by maintainers) ## License diff --git a/RELEASING.md b/RELEASING.md index 646cf53..c42f9f9 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,81 +2,134 @@ This document describes how to create a new release of swift-ejson. +## Version Management + +**Version is defined in code** - The single source of truth for version information is: +``` +Sources/EJSONKit/Version.swift +``` + +All version references (binary, releases, tags, Homebrew formula) automatically sync from this file. + ## Prerequisites - Write access to the repository - All tests passing on main branch -- Updated CHANGELOG (if applicable) +- All changes merged to main -## Release Steps +## Recommended Release Process (Automated) -### 1. Prepare the Release +### 1. Update the Version -Ensure all changes for the release are merged to the main branch: +Edit `Sources/EJSONKit/Version.swift` and update the version: -```bash -git checkout main -git pull origin main +```swift +public static let current = "1.0.0" // Change to your new version ``` -### 2. Update Version References - -Update version references in documentation if needed: - -- README.md (example version numbers in CLI installation) -- Package.swift (if you maintain a version constant) - -### 3. Create and Push a Tag - -Create a version tag following semantic versioning (vMAJOR.MINOR.PATCH): +Commit and push to main: ```bash -# Create an annotated tag -git tag -a v1.0.0 -m "Release version 1.0.0" +git checkout main +git add Sources/EJSONKit/Version.swift +git commit -m "Bump version to 1.0.0" +git push origin main +``` + +### 2. Trigger the Release Workflow -# Push the tag to GitHub -git push origin v1.0.0 +Go to the GitHub Actions page: ``` +https://github.com/diogot/swift-ejson/actions/workflows/create-release.yml +``` + +1. Click "Run workflow" +2. Type "release" to confirm +3. Click "Run workflow" button -### 4. Automated Build Process +### 3. Automated Process -Once you push the tag, GitHub Actions will automatically: +The workflow will automatically: -1. **Run Tests** - Ensure all tests pass on macOS and Linux -2. **Build macOS Binary** - Create a universal binary (x86_64 + ARM64) -3. **Create Archive** - Package the binary as `.tar.gz` -4. **Calculate Checksums** - Generate SHA256 checksums -5. **Create GitHub Release** - Publish release with all artifacts -6. **Upload Assets** - Attach binaries and checksums +1. โœ… **Validate** - Extract version from code, check tests pass +2. ๐Ÿ—๏ธ **Build** - Create universal macOS binary (x86_64 + ARM64) +3. ๐Ÿท๏ธ **Tag** - Create and push git tag (e.g., v1.0.0) +4. ๐Ÿ“ฆ **Release** - Create GitHub release with binaries +5. ๐Ÿบ **Update Formula** - Update Homebrew formula with SHA256 +6. โœ… **Commit** - Push updated formula to main -You can monitor the progress at: `https://github.com/diogot/swift-ejson/actions` +Monitor progress at: `https://github.com/diogot/swift-ejson/actions` -### 5. Verify the Release +### 4. Verify the Release After the workflow completes: 1. Go to https://github.com/diogot/swift-ejson/releases 2. Verify the release was created with: - - Release notes + - Correct version number - Binary archive (`.tar.gz`) - Checksum file (`.sha256`) + - Release notes + +### 5. Test the Installation + +Test the Homebrew installation: + +```bash +# Update Homebrew +brew update -### 6. Test the Release +# Install via direct URL +brew install https://raw.githubusercontent.com/diogot/swift-ejson/main/Formula/ejson.rb -Download and test the binary: +# Or via tap (if set up) +brew tap diogot/ejson +brew install ejson + +# Verify version +ejson --version +``` + +Test manual download: ```bash VERSION="1.0.0" curl -L "https://github.com/diogot/swift-ejson/releases/download/v${VERSION}/ejson-${VERSION}-macos-universal.tar.gz" | tar xz -./ejson help -./ejson keygen +./ejson --version +``` + +## Alternative: Manual Release Process + +If you prefer manual control or the automated workflow fails: + +### 1. Update Version in Code + +```bash +# Edit Sources/EJSONKit/Version.swift +git add Sources/EJSONKit/Version.swift +git commit -m "Bump version to 1.0.0" +git push origin main +``` + +### 2. Create and Push Tag + +```bash +VERSION=$(./scripts/get-version.sh) +git tag -a v${VERSION} -m "Release version ${VERSION}" +git push origin v${VERSION} ``` -Verify the checksum: +This triggers the tag-based release workflow (release.yml). + +### 3. Update Homebrew Formula + +After the release is created: ```bash -curl -L "https://github.com/diogot/swift-ejson/releases/download/v${VERSION}/ejson-${VERSION}-macos-universal.tar.gz.sha256" -o ejson.sha256 -shasum -a 256 -c ejson.sha256 +./scripts/update-formula.sh 1.0.0 +git add Formula/ejson.rb +git commit -m "Update Homebrew formula to v1.0.0" +git push origin main ``` ## Versioning Guidelines @@ -144,7 +197,34 @@ rm -rf .build release After releasing: -1. Announce the release (Twitter, forums, etc.) -2. Update documentation sites if applicable -3. Close related issues/PRs -4. Plan next release milestone +1. **Update Separate Tap (Optional)**: + + If using a separate `homebrew-ejson` tap repository: + ```bash + cd ../homebrew-ejson + cp ../swift-ejson/Formula/ejson.rb Formula/ + git add Formula/ejson.rb + git commit -m "Update ejson to v1.0.0" + git push + ``` + + Note: The formula in the main repo is already updated automatically. + +2. Announce the release (Twitter, forums, etc.) +3. Update documentation sites if applicable +4. Close related issues/PRs +5. Plan next release milestone + +## Version Synchronization + +The version system ensures everything stays in sync: + +| Component | Source | How it Syncs | +|-----------|--------|--------------| +| **Library Version** | `Sources/EJSONKit/Version.swift` | Direct reference | +| **Binary Version** | CLI `--version` flag | Imports from EJSONKit.Version | +| **Git Tag** | GitHub Actions | Extracted via `scripts/get-version.sh` | +| **GitHub Release** | GitHub Actions | Uses git tag | +| **Homebrew Formula** | `Formula/ejson.rb` | Auto-updated by workflow | + +**To change version**: Edit `Sources/EJSONKit/Version.swift` only. Everything else updates automatically. diff --git a/Sources/EJSONKit/Version.swift b/Sources/EJSONKit/Version.swift new file mode 100644 index 0000000..5ad0d28 --- /dev/null +++ b/Sources/EJSONKit/Version.swift @@ -0,0 +1,17 @@ +/// Version information for EJSONKit and ejson CLI +/// +/// This is the single source of truth for version information. +/// All other version references should derive from this. +public enum Version { + /// The current version of EJSONKit + /// + /// Update this value when preparing a new release. + /// Format: MAJOR.MINOR.PATCH following Semantic Versioning + public static let current = "1.0.0" + + /// Full version string with additional information + public static let full = "ejson version \(current)" + + /// Version description + public static let description = "Swift EJSON - Compatible with Shopify EJSON" +} diff --git a/Sources/ejson/main.swift b/Sources/ejson/main.swift index 9eacda2..a483fbc 100644 --- a/Sources/ejson/main.swift +++ b/Sources/ejson/main.swift @@ -210,6 +210,11 @@ func decryptCommand(args: [String]) { } } +func printVersion() { + print(Version.full) + print(Version.description) +} + func printUsage() { print(""" Usage: ejson [options] @@ -260,6 +265,8 @@ func main() { decryptCommand(args: commandArgs) case "-h", "--help", "help": printUsage() + case "-v", "--version", "version": + printVersion() default: exitWithError("Unknown command: \(command)\nRun 'ejson help' for usage information") } diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md new file mode 100644 index 0000000..9e5f540 --- /dev/null +++ b/VERSION_MANAGEMENT.md @@ -0,0 +1,176 @@ +# Version Management + +## Single Source of Truth + +The version for swift-ejson is defined in **one place only**: + +``` +Sources/EJSONKit/Version.swift +``` + +All other components automatically sync from this file: +- CLI binary `--version` output +- Git tags +- GitHub releases +- Homebrew formula + +## Updating the Version + +To release a new version: + +1. **Edit the version file**: + ```swift + // Sources/EJSONKit/Version.swift + public static let current = "1.1.0" // Update this line + ``` + +2. **Commit and push**: + ```bash + git add Sources/EJSONKit/Version.swift + git commit -m "Bump version to 1.1.0" + git push origin main + ``` + +3. **Trigger the release**: + - Go to GitHub Actions: https://github.com/diogot/swift-ejson/actions/workflows/create-release.yml + - Click "Run workflow" + - Type "release" to confirm + - The workflow will: + - Extract version from code + - Create git tag (v1.1.0) + - Build binaries + - Create GitHub release + - Update Homebrew formula + +## How It Works + +### Version Extraction + +The script `scripts/get-version.sh` extracts the version from code: + +```bash +VERSION=$(./scripts/get-version.sh) +echo $VERSION # Outputs: 1.0.0 +``` + +This is used by: +- Manual release commands +- GitHub Actions workflows +- Build scripts + +### CLI Binary + +The binary imports the version directly: + +```swift +// Sources/ejson/main.swift +import EJSONKit + +func printVersion() { + print(Version.full) // "ejson version 1.0.0" + print(Version.description) // "Swift EJSON - Compatible with Shopify EJSON" +} +``` + +### GitHub Actions + +The `create-release.yml` workflow: + +1. Runs `./scripts/get-version.sh` to extract version +2. Validates the version format +3. Checks if tag already exists +4. Creates tag, release, and updates formula automatically + +### Homebrew Formula + +The formula is automatically updated by the release workflow with: +- Correct version number +- Download URL with version +- SHA256 checksum of the binary + +## Version Format + +We follow [Semantic Versioning](https://semver.org/): + +``` +MAJOR.MINOR.PATCH +``` + +Examples: +- `1.0.0` - Initial release +- `1.1.0` - New features (backward compatible) +- `1.0.1` - Bug fixes +- `2.0.0` - Breaking changes + +The version must match the regex: `^[0-9]+\.[0-9]+\.[0-9]+$` + +## Validation + +The version extraction script validates: +- File exists (`Sources/EJSONKit/Version.swift`) +- Version can be extracted +- Format is valid (MAJOR.MINOR.PATCH) + +The release workflow validates: +- Version format is correct +- Tag doesn't already exist +- Tests pass before releasing + +## Manual Override + +If you need to bypass the automated workflow: + +```bash +# Get current version from code +VERSION=$(./scripts/get-version.sh) + +# Create tag manually +git tag -a v${VERSION} -m "Release version ${VERSION}" +git push origin v${VERSION} + +# This triggers the tag-based release workflow (release.yml) +# Then manually update the formula: +./scripts/update-formula.sh ${VERSION} +``` + +## Benefits + +1. **No version drift** - Binary, releases, and formula always match +2. **Single update point** - Change version in one place +3. **Automated releases** - Less manual work, fewer errors +4. **Validation** - Prevents duplicate tags and invalid versions +5. **Traceable** - Version changes are visible in git history + +## Checking Current Version + +```bash +# From code +./scripts/get-version.sh + +# From binary (if built) +.build/release/ejson --version + +# From installed binary +ejson --version +``` + +## For Contributors + +When working on features: +- **Don't** manually update version numbers in PRs +- **Do** wait for maintainers to bump version before release +- Version bumps happen on `main` branch only +- Releases are created from `main` branch + +## For Maintainers + +Release checklist: +1. โœ… All PRs merged to main +2. โœ… All tests passing +3. โœ… Update `Sources/EJSONKit/Version.swift` +4. โœ… Commit version bump +5. โœ… Run manual release workflow +6. โœ… Verify release was created +7. โœ… Test installation + +See [RELEASING.md](RELEASING.md) for detailed instructions. diff --git a/scripts/get-version.sh b/scripts/get-version.sh new file mode 100755 index 0000000..f9b64cc --- /dev/null +++ b/scripts/get-version.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +# Extract version from Version.swift +# This is the single source of truth for version information + +VERSION_FILE="Sources/EJSONKit/Version.swift" + +if [ ! -f "$VERSION_FILE" ]; then + echo "Error: Version file not found at $VERSION_FILE" >&2 + exit 1 +fi + +# Extract version string from: public static let current = "1.0.0" +VERSION=$(grep -E 'static let current = "' "$VERSION_FILE" | sed -E 's/.*"([^"]+)".*/\1/') + +if [ -z "$VERSION" ]; then + echo "Error: Could not extract version from $VERSION_FILE" >&2 + exit 1 +fi + +# Validate version format (MAJOR.MINOR.PATCH) +if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: Invalid version format: $VERSION (expected MAJOR.MINOR.PATCH)" >&2 + exit 1 +fi + +echo "$VERSION" diff --git a/scripts/update-formula.sh b/scripts/update-formula.sh new file mode 100755 index 0000000..61d8a6b --- /dev/null +++ b/scripts/update-formula.sh @@ -0,0 +1,82 @@ +#!/bin/bash +set -e + +# Script to update the Homebrew formula with the correct SHA256 checksum +# Usage: ./scripts/update-formula.sh +# Example: ./scripts/update-formula.sh 1.0.0 + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + echo "Example: $0 1.0.0" + exit 1 +fi + +VERSION=$1 +FORMULA_FILE="Formula/ejson.rb" +RELEASE_URL="https://github.com/diogot/swift-ejson/releases/download/v${VERSION}/ejson-${VERSION}-macos-universal.tar.gz" + +echo "Updating Homebrew formula for version ${VERSION}..." + +# Check if formula file exists +if [ ! -f "$FORMULA_FILE" ]; then + echo "Error: Formula file not found at $FORMULA_FILE" + exit 1 +fi + +# Download the release tarball +echo "Downloading release tarball..." +TEMP_FILE=$(mktemp) +trap "rm -f $TEMP_FILE" EXIT + +if ! curl -L -f -o "$TEMP_FILE" "$RELEASE_URL"; then + echo "Error: Failed to download release from $RELEASE_URL" + echo "Make sure the release v${VERSION} exists on GitHub" + exit 1 +fi + +# Calculate SHA256 +echo "Calculating SHA256 checksum..." +if command -v sha256sum &> /dev/null; then + SHA256=$(sha256sum "$TEMP_FILE" | awk '{print $1}') +elif command -v shasum &> /dev/null; then + SHA256=$(shasum -a 256 "$TEMP_FILE" | awk '{print $1}') +else + echo "Error: Neither sha256sum nor shasum found" + exit 1 +fi + +echo "SHA256: $SHA256" + +# Create backup of formula +cp "$FORMULA_FILE" "${FORMULA_FILE}.backup" + +# Update the formula +echo "Updating formula..." + +# Update version +sed -i.tmp "s/version \".*\"/version \"${VERSION}\"/" "$FORMULA_FILE" + +# Update URL +sed -i.tmp "s|download/v[0-9.]\+/ejson-[0-9.]\+-macos-universal.tar.gz|download/v${VERSION}/ejson-${VERSION}-macos-universal.tar.gz|g" "$FORMULA_FILE" + +# Update SHA256 (replace PLACEHOLDER or existing hash) +sed -i.tmp "s/sha256 \".*\"/sha256 \"${SHA256}\"/" "$FORMULA_FILE" + +# Clean up sed backup files +rm -f "${FORMULA_FILE}.tmp" + +echo "" +echo "Formula updated successfully!" +echo "" +echo "Changes made to $FORMULA_FILE:" +echo " - version: ${VERSION}" +echo " - sha256: ${SHA256}" +echo "" +echo "Please review the changes:" +git diff "$FORMULA_FILE" || diff -u "${FORMULA_FILE}.backup" "$FORMULA_FILE" || true +echo "" +echo "If everything looks good:" +echo " 1. Commit: git add $FORMULA_FILE && git commit -m \"Update formula to v${VERSION}\"" +echo " 2. Push: git push" +echo "" +echo "To restore the backup: mv ${FORMULA_FILE}.backup $FORMULA_FILE"