# Plugin Update Workflow **Last Updated:** January 20, 2026 **Version:** 1.8.4 **Status:** Production **Scope:** How to release plugin updates and what happens automatically vs manually --- ## 🎯 Quick Start: Simplified Release Process The plugin release process has been **simplified** to require only 3 fields: 1. **Version** (e.g., 1.3.3) 2. **Changelog** (what's new) 3. **Status** (draft → released) All other fields are either: - ✅ Auto-filled from previous version - ✅ Auto-generated on release (file path, size, checksum) - ✅ Auto-calculated (version code) **Release in 3 clicks:** ``` Django Admin → Add Plugin Version → Enter 3 fields → Save → Change status to 'released' ``` --- ## Recent Release History (v1.7.0) ### WordPress Plugin Progression | Version | Date | Type | Key Changes | |---------|------|------|-------------| | 1.5.1 | Jan 10, 2026 | Minor | Admin UI consolidation: 6→3 pages (Dashboard, Settings, Logs), header alignment fixes | | 1.5.0 | Jan 10, 2026 | Minor | Authentication simplification: Site.wp_api_key as single source of truth | | 1.3.3 | Jan 10, 2026 | Patch | Template design: Square image grid fixes, landscape image positioning, direct border-radius/shadow on images without captions | | 1.3.2 | Jan 9, 2026 | Patch | Template fixes in app and plugin, image layout improvements | | 1.3.1 | Jan 9, 2026 | Patch | WordPress plugin versioning updates | | 1.3.0 | Jan 8, 2026 | Minor | Initial distribution system release, automated updates | ### Distribution System Status - ✅ Automated ZIP generation: Operational - ✅ Checksum calculation: MD5 + SHA256 - ✅ WordPress auto-update: Active - ✅ Health checks: Implemented - ✅ Installation tracking: Complete --- ## Table of Contents 1. [Update Workflow Overview](#1-update-workflow-overview) 2. [What Happens Automatically](#2-what-happens-automatically) 3. [What Requires Manual Action](#3-what-requires-manual-action) 4. [Step-by-Step: Releasing a New Version](#4-step-by-step-releasing-a-new-version) 5. [Post-Update Verification Checklist](#5-post-update-verification-checklist) 6. [Version Numbering](#6-version-numbering) 7. [Rollback Procedure](#7-rollback-procedure) 8. [Emergency Updates](#8-emergency-updates) 9. [Recent Updates & Lessons Learned](#9-recent-updates--lessons-learned) --- ## 1. Update Workflow Overview ### The Big Picture ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Plugin Update Lifecycle │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ │ │ │ 1. DEVELOP │ Make changes to plugin source code │ │ │ ↓ │ Location: /plugins/wordpress/source/igny8-wp-bridge/ │ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ 2. VERSION │ Update version in PHP header + constant │ │ │ ↓ │ File: igny8-bridge.php │ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ 3. RELEASE │ Create PluginVersion in Django admin │ │ │ ↓ │ Set status = "released" or "update_ready" │ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ 4. AUTO-BUILD│ ✅ AUTOMATIC: ZIP created, checksums calculated │ │ │ ↓ │ Signal handler builds package on status change │ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ 5. VERIFY │ Test download, check contents, verify endpoints │ │ │ ↓ │ See verification checklist below │ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ 6. AVAILABLE │ WordPress sites can now see and install update │ │ │ │ Users update via WP admin or auto-update │ │ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` --- ## 2. What Happens Automatically When you change a `PluginVersion` status to `released` or `update_ready`, the following happens **automatically** via Django signals: ### ✅ Automatic Actions | Action | Details | |--------|---------| | **ZIP Creation** | Source files packaged into distribution ZIP | | **Version Update** | Version number updated in PHP files inside ZIP | | **File Cleanup** | Tests, .git, __pycache__, .bak files removed from ZIP | | **Checksum Calculation** | SHA256 hash generated | | **File Size Recording** | Byte count stored in database | | **File Path Update** | `file_path` field populated | | **Released Date** | `released_at` timestamp set | | **Symlink Update** | `*-latest.zip` symlink updated | ### How It Works (Signal Handler) ```python # backend/igny8_core/plugins/signals.py @receiver(pre_save, sender=PluginVersion) def auto_build_plugin_on_release(sender, instance, **kwargs): """ Triggered when PluginVersion.status changes to: - 'released' - 'update_ready' Actions: 1. Calls create_plugin_zip() utility 2. Updates instance.file_path 3. Updates instance.file_size 4. Updates instance.checksum 5. Sets instance.released_at """ ``` ### What WordPress Sites See Once released, the `check-update` API returns: ```json { "update_available": true, "latest_version": "1.2.0", "download_url": "https://api.igny8.com/api/plugins/igny8-wp-bridge/download/", "changelog": "Your changelog here" } ``` WordPress will show "Update Available" in: - Plugins page - Dashboard → Updates - Admin bar (if enabled) --- ## 3. What Requires Manual Action ### ❌ Manual Steps Required | Action | When | How | |--------|------|-----| | **Update Source Version** | Before release | Edit `igny8-bridge.php` header | | **Create PluginVersion Record** | Each release | Django admin: just enter version, changelog, status | | **Verify After Release** | After status change | Run verification checklist | | **Mark Old Version Deprecated** | After successful release | Change old version status | **Note:** The form is simplified - most fields auto-fill from previous version or are auto-generated. ### Source Version Update Locations When releasing a new version, update these in the source: **File:** `/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php` ```php /** * Plugin Name: IGNY8 WordPress Bridge * Version: 1.2.0 ← UPDATE THIS */ define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← AND THIS ``` **Note:** The ZIP build process also updates these, but it's good practice to keep source in sync. --- ## 4. Step-by-Step: Releasing a New Version ### The Simplified Process ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Simplified Version Release (3 Required Fields) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Django Admin → Add Plugin Version │ │ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ Plugin: [IGNY8 WordPress Bridge ▼] ← Select plugin │ │ │ │ Version: [1.2.0] ← New version │ │ │ │ Status: [draft ▼] ← Start as draft │ │ │ │ Changelog: [Bug fixes and improvements] ← What's new │ │ │ │ │ │ │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ │ │ Everything below is AUTO-FILLED or AUTO-GENERATED: │ │ │ │ │ │ │ │ Min API Version: 1.0 (from previous version) │ │ │ │ Min PHP Version: 7.4 (from previous version) │ │ │ │ File Path: (empty) → Auto-generated on release │ │ │ │ File Size: (empty) → Auto-calculated on release │ │ │ │ Checksum: (empty) → Auto-calculated on release │ │ │ │ Version Code: (empty) → Auto-calculated from version │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ Click [Save] → Status changes to 'released' → ZIP builds automatically │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### Step 1: Make Code Changes ```bash cd /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/ # Make your changes to PHP files # Test locally if possible ``` ### Step 2: Update Version Number Edit `igny8-bridge.php`: ```php /** * Plugin Name: IGNY8 WordPress Bridge * Version: 1.2.0 ← New version */ define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← Match here ``` ### Step 3: Create PluginVersion in Django Admin 1. Go to: `https://api.igny8.com/backend/` 2. Navigate to: **Plugin Distribution** → **Plugin Versions** 3. Click **Add Plugin Version** 4. Fill in **ONLY** these required fields: - **Plugin:** IGNY8 WordPress Bridge - **Version:** 1.2.0 - **Status:** draft (initially) - **Changelog:** Describe changes 5. Click **Save** **Note:** All other fields are auto-filled: - `min_api_version`, `min_platform_version`, `min_php_version` → Copied from previous version - `file_path`, `file_size`, `checksum` → Auto-generated when you release - `version_code` → Auto-calculated from version number ### Step 4: Release the Version **Option A: Release via Status Change** 1. Edit the PluginVersion you just created 2. Change **Status** to `released` 3. Click **Save** **Option B: Release via Bulk Action** 1. Select the version(s) in the list 2. Choose **Actions** → **✅ Release selected versions** 3. Click **Go** **What happens:** Signal triggers auto-build → ZIP created → database updated with file info **Note:** You can also use the action **📢 Mark as update ready** to immediately notify WordPress sites. ### Step 5: Verify Release Run the [verification checklist](#5-post-update-verification-checklist) below. ### Step 6: Deprecate Old Version (Optional) 1. Find the previous version in Django admin 2. Change **Status** to `deprecated` 3. Save --- ## 5. Post-Update Verification Checklist ### Quick Verification Script Run this single command to verify everything: ```bash #!/bin/bash # Complete Plugin Release Verification Script # Usage: Save as verify-plugin.sh and run: bash verify-plugin.sh 1.1.2 VERSION=${1:-"latest"} PLUGIN_SLUG="igny8-wp-bridge" echo "==========================================" echo "Plugin Release Verification: v${VERSION}" echo "==========================================" echo "" # 1. Check ZIP file exists echo "1. Checking ZIP file..." if [ "$VERSION" = "latest" ]; then ls -lh /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip 2>/dev/null && echo " ✓ Latest ZIP exists" || echo " ✗ Latest ZIP not found" else ls -lh /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-v${VERSION}.zip 2>/dev/null && echo " ✓ ZIP v${VERSION} exists" || echo " ✗ ZIP v${VERSION} not found" fi echo "" # 2. Check symlink echo "2. Checking symlink..." ls -la /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip | grep -q "^l" && echo " ✓ Symlink valid" || echo " ✗ Symlink missing" echo "" # 3. Test download endpoint echo "3. Testing download endpoint..." HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/download/) FILE_SIZE=$(curl -s -o /dev/null -w "%{size_download}" https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/download/) if [ "$HTTP_CODE" = "200" ]; then echo " ✓ Download works: ${HTTP_CODE} - ${FILE_SIZE} bytes ($(( FILE_SIZE / 1024 )) KB)" else echo " ✗ Download failed: HTTP ${HTTP_CODE}" fi echo "" # 4. Test check-update endpoint echo "4. Testing check-update endpoint..." UPDATE_RESPONSE=$(curl -s "https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/check-update/?current_version=1.0.0") LATEST_VERSION=$(echo "$UPDATE_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('latest_version', 'N/A'))" 2>/dev/null) if [ -n "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "N/A" ]; then echo " ✓ Check-update works: Latest version = $LATEST_VERSION" else echo " ✗ Check-update failed" fi echo "" # 5. Test info endpoint echo "5. Testing info endpoint..." INFO_RESPONSE=$(curl -s "https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/info/") PLUGIN_NAME=$(echo "$INFO_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('name', 'N/A'))" 2>/dev/null) if [ -n "$PLUGIN_NAME" ] && [ "$PLUGIN_NAME" != "N/A" ]; then echo " ✓ Info endpoint works: $PLUGIN_NAME" else echo " ✗ Info endpoint failed" fi echo "" # 6. Verify version in ZIP echo "6. Verifying version in ZIP..." if [ "$VERSION" != "latest" ]; then ZIP_VERSION=$(unzip -p /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-v${VERSION}.zip ${PLUGIN_SLUG}/igny8-bridge.php 2>/dev/null | grep -E "Version:" | head -1 | awk '{print $3}') if [ "$ZIP_VERSION" = "$VERSION" ]; then echo " ✓ ZIP version matches: $ZIP_VERSION" else echo " ✗ ZIP version mismatch: expected $VERSION, got $ZIP_VERSION" fi else echo " - Skipped (use specific version to check)" fi echo "" # 7. Verify API URL echo "7. Verifying API URL in ZIP..." API_COUNT=$(unzip -p /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip ${PLUGIN_SLUG}/igny8-bridge.php 2>/dev/null | grep -c "api.igny8.com") if [ "$API_COUNT" -gt 0 ]; then echo " ✓ API URL correct: api.igny8.com found (${API_COUNT} occurrences)" else echo " ✗ API URL incorrect: api.igny8.com not found" fi echo "" # 8. Check database echo "8. Checking database..." docker exec igny8_backend python manage.py shell -c " from igny8_core.plugins.models import Plugin, PluginVersion try: p = Plugin.objects.get(slug='${PLUGIN_SLUG}') v = p.get_latest_version() if v: print(f' ✓ Latest version: {v.version}') print(f' ✓ Status: {v.status}') print(f' ✓ File: {v.file_path}') print(f' ✓ Size: {v.file_size:,} bytes ({v.file_size/1024:.1f} KB)') print(f' ✓ Checksum: {v.checksum[:32]}...') else: print(' ✗ No released version found') except Exception as e: print(f' ✗ Error: {e}') " 2>/dev/null | grep -E "✓|✗" echo "" echo "==========================================" echo "Verification Complete" echo "==========================================" ``` **Quick run (copy-paste):** ```bash # Verify latest version curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/info/" | python3 -m json.tool && \ curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0" | python3 -m json.tool && \ ls -lh /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-latest.zip && \ echo "✓ All endpoints working" ``` ### Database Verification ```bash # Check database has correct values docker exec igny8_backend python manage.py shell -c " from igny8_core.plugins.models import Plugin, PluginVersion p = Plugin.objects.get(slug='igny8-wp-bridge') v = p.get_latest_version() print(f'Version: {v.version}') print(f'Status: {v.status}') print(f'File path: {v.file_path}') print(f'File size: {v.file_size}') print(f'Checksum: {v.checksum[:32]}...') print(f'Released at: {v.released_at}') " ``` ### Expected Results | Check | Expected | |-------|----------| | Download returns | 200 status, ~150-200KB | | check-update shows | `"latest_version": "1.2.0"` | | info shows | `"version": "1.2.0"` | | ZIP version header | `Version: 1.2.0` | | API URL | `api.igny8.com` (NOT app.igny8.com) | ### WordPress Verification (If Possible) 1. Go to a test WordPress site with plugin installed 2. Navigate to **Dashboard** → **Updates** → **Check Again** 3. Verify "IGNY8 WordPress Bridge" shows update available 4. Click "View version details" → verify changelog appears 5. Click "Update Now" → verify update completes successfully --- ## 6. Version Numbering ### Semantic Versioning Format: `MAJOR.MINOR.PATCH` (e.g., 1.2.3) | Part | When to Increment | |------|-------------------| | **MAJOR** | Breaking changes, incompatible API changes | | **MINOR** | New features, backwards compatible | | **PATCH** | Bug fixes, minor improvements | ### Version Code Calculation Used for numeric comparison in database: ``` 1.0.0 → 10000 1.0.1 → 10001 1.2.0 → 10200 1.2.3 → 10203 2.0.0 → 20000 ``` Formula: `(MAJOR * 10000) + (MINOR * 100) + PATCH` --- ## 7. Rollback Procedure ### If Update Causes Issues **Option 1: Quick Rollback via Database** ```bash # Make old version the "latest" by changing status docker exec igny8_backend python manage.py shell -c " from igny8_core.plugins.models import PluginVersion # Demote new version new = PluginVersion.objects.get(plugin__slug='igny8-wp-bridge', version='1.2.0') new.status = 'deprecated' new.save() # Promote old version back old = PluginVersion.objects.get(plugin__slug='igny8-wp-bridge', version='1.1.1') old.status = 'released' old.save() print('Rollback complete') " ``` **Option 2: Keep Old ZIP Files** Old ZIP files are preserved in `dist/`. To serve an old version: ```bash # Update symlink to point to old version cd /data/app/igny8/plugins/wordpress/dist/ ln -sf igny8-wp-bridge-v1.1.1.zip igny8-wp-bridge-latest.zip ``` ### Retention Policy Keep at least 3 previous version ZIPs for emergency rollback: - Current release - Previous release - One before that --- ## 8. Emergency Updates ### For Critical Security Fixes 1. **Set `force_update = True`** on the new PluginVersion 2. This flag signals WordPress sites that update is mandatory 3. Sites with auto-updates enabled will update immediately ### Emergency Release Checklist ```bash # 1. Make the fix in source cd /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/ # ... make changes ... # 2. Update version (use PATCH increment) # Edit igny8-bridge.php # 3. Create version in Django admin with: # - Status: update_ready # - Force Update: ✓ (checked) # - Changelog: "SECURITY: [description]" # 4. Verify immediately curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0 # Response should include: # "force_update": true ``` --- ## 9. Recent Updates & Lessons Learned ### v1.3.3 Release (Jan 10, 2026) **Changes:** - Fixed square image grid layout (side-by-side display) - Fixed landscape image positioning in sections 4+ - Removed card wrapper for images without captions - Applied border-radius/shadow directly to images - Landscape images now appear after first paragraph **Process:** - Source code updated in plugin templates and CSS - Version bumped in `igny8-bridge.php` (header + constant) - Django admin: Created v1.3.3 with changelog - Status changed to "released" → ZIP auto-generated - Verified download endpoint and file contents - Template changes tested on staging WordPress site **Lessons:** - CSS changes require thorough cross-browser testing - Image layout fixes need responsive design verification - Template changes should be tested with multiple content types ### v1.3.2 Release (Jan 9, 2026) **Changes:** - Template rendering improvements - Image layout enhancements - Content section fixes **Process:** - Standard release workflow - Auto-build successful - No rollback needed ### v1.3.0 Release (Jan 8, 2026) **Changes:** - Initial distribution system implementation - WordPress auto-update mechanism - Base template system **Process:** - Major release with new infrastructure - Extensive testing required - First use of automated packaging ### Distribution System Milestones **v1.7.0 Infrastructure:** - ✅ Complete plugin distribution system - ✅ Multi-platform support architecture - ✅ Automated versioning and packaging - ✅ Security features (checksums, signed URLs) - ✅ Monitoring and analytics **Best Practices Established:** 1. Always test download endpoint after release 2. Verify ZIP contents match source 3. Check version number in extracted files 4. Test update notification in WordPress 5. Monitor download analytics **Common Issues Resolved:** - ZIP generation timing → Now synchronous in signals - Checksum mismatches → Auto-calculated reliably - Version comparison → Semantic versioning logic fixed - File size tracking → Automatic and accurate --- ## Quick Reference Card ### Release New Version (Simplified) ``` 1. Edit source code in /plugins/wordpress/source/igny8-wp-bridge/ 2. Update version in igny8-bridge.php (header + constant) 3. Django Admin → Add Plugin Version: - Plugin: IGNY8 WordPress Bridge - Version: 1.3.3 (or next version) - Changelog: Describe changes - Status: draft - (All other fields auto-fill) 4. Change status to "released" (or use bulk action) → Auto-build triggers 5. Run verification checklist 6. Optionally: Mark old version as deprecated ``` **Admin Bulk Actions:** - **✅ Release selected versions** - Builds ZIP and marks as released - **📢 Mark as update ready** - Notifies WordPress sites - **🗑️ Mark as deprecated** - Deprecates old versions ### Verification Commands ```bash # Test all endpoints curl -I https://api.igny8.com/api/plugins/igny8-wp-bridge/download/ curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0 curl https://api.igny8.com/api/plugins/igny8-wp-bridge/info/ # Check ZIP contents unzip -l /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v*.zip | head -20 ``` ### Emergency Rollback ```bash # Swap versions in database docker exec igny8_backend python manage.py shell -c " from igny8_core.plugins.models import PluginVersion PluginVersion.objects.filter(version='NEW').update(status='deprecated') PluginVersion.objects.filter(version='OLD').update(status='released') " ``` --- ## Related Documentation - [WORDPRESS-INTEGRATION.md](WORDPRESS-INTEGRATION.md) - Full integration guide - [docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md](/docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md) - Original system design