diff --git a/backend/igny8_core/plugins/admin.py b/backend/igny8_core/plugins/admin.py index 0e6a39a8..30670d97 100644 --- a/backend/igny8_core/plugins/admin.py +++ b/backend/igny8_core/plugins/admin.py @@ -1,6 +1,7 @@ """ Django Admin Configuration for Plugin Distribution System """ +from django import forms from django.contrib import admin from django.utils.html import format_html from django.urls import reverse @@ -8,6 +9,68 @@ from unfold.admin import ModelAdmin, TabularInline from .models import Plugin, PluginVersion, PluginInstallation, PluginDownload +class PluginVersionForm(forms.ModelForm): + """ + Simplified form for creating new plugin versions. + + Auto-fills most fields from the latest version, only requires: + - Plugin (select) + - Version number + - Changelog + - Status (defaults to 'draft') + + All other fields are either: + - Auto-filled from previous version (min_api_version, min_php_version, etc.) + - Auto-generated on release (file_path, file_size, checksum) + - Auto-calculated (version_code) + """ + + class Meta: + model = PluginVersion + fields = '__all__' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # If this is a new version (no instance), auto-fill from latest + if not self.instance.pk: + # Check if plugin is already selected (from initial data or POST data) + plugin_id = None + + # Try to get plugin from POST data (when form is submitted) + if self.data: + plugin_id = self.data.get('plugin') + # Try to get plugin from initial data (when form is pre-filled) + elif self.initial: + plugin_id = self.initial.get('plugin') + + if plugin_id: + try: + plugin = Plugin.objects.get(pk=plugin_id) + latest = plugin.get_latest_version() + + if latest: + # Auto-fill from latest version (only if not POST) + if not self.data: + if 'min_api_version' in self.fields: + self.fields['min_api_version'].initial = latest.min_api_version + if 'min_platform_version' in self.fields: + self.fields['min_platform_version'].initial = latest.min_platform_version + if 'min_php_version' in self.fields: + self.fields['min_php_version'].initial = latest.min_php_version + if 'force_update' in self.fields: + self.fields['force_update'].initial = False + except (Plugin.DoesNotExist, ValueError, TypeError): + pass + + # Set helpful help texts for fields that exist in the form + # (some fields may be readonly and not in self.fields) + if 'version' in self.fields: + self.fields['version'].help_text = "Semantic version (e.g., 1.2.0)" + if 'changelog' in self.fields: + self.fields['changelog'].help_text = "What's new in this version" + + class PluginVersionInline(TabularInline): """Inline admin for plugin versions.""" model = PluginVersion @@ -66,35 +129,37 @@ class PluginAdmin(ModelAdmin): class PluginVersionAdmin(ModelAdmin): """Admin configuration for PluginVersion model.""" + form = PluginVersionForm + list_display = ['plugin', 'version', 'status', 'file_size_display', 'download_count', 'released_at'] list_filter = ['plugin', 'status', 'released_at'] search_fields = ['plugin__name', 'version', 'changelog'] - readonly_fields = ['version_code', 'created_at', 'download_count'] + readonly_fields = ['version_code', 'file_path', 'file_size', 'checksum', 'created_at', 'released_at', 'download_count'] fieldsets = [ - ('Version Info', { - 'fields': ['plugin', 'version', 'version_code', 'status'] + ('Required Fields', { + 'fields': ['plugin', 'version', 'status', 'changelog'], + 'description': 'Only these fields are required. Others are auto-filled from previous version or auto-generated.' }), - ('File Info', { - 'fields': ['file_path', 'file_size', 'checksum'] - }), - ('Requirements', { - 'fields': ['min_api_version', 'min_platform_version', 'min_php_version'] - }), - ('Release', { - 'fields': ['changelog', 'force_update', 'released_at'] - }), - ('Statistics', { - 'fields': ['download_count'], + ('Requirements (Auto-filled from previous version)', { + 'fields': ['min_api_version', 'min_platform_version', 'min_php_version'], 'classes': ['collapse'] }), - ('Timestamps', { - 'fields': ['created_at'], + ('File Info (Auto-generated on release)', { + 'fields': ['file_path', 'file_size', 'checksum'], + 'classes': ['collapse'] + }), + ('Advanced Options', { + 'fields': ['version_code', 'force_update'], + 'classes': ['collapse'] + }), + ('Metadata', { + 'fields': ['released_at', 'download_count', 'created_at'], 'classes': ['collapse'] }), ] - actions = ['release_versions', 'mark_as_update_ready'] + actions = ['release_versions', 'mark_as_update_ready', 'mark_as_deprecated'] def file_size_display(self, obj): if obj.file_size: @@ -109,19 +174,25 @@ class PluginVersionAdmin(ModelAdmin): return obj.get_download_count() download_count.short_description = 'Downloads' - @admin.action(description="Release selected versions") + @admin.action(description="✅ Release selected versions (builds ZIP automatically)") def release_versions(self, request, queryset): from django.utils import timezone - count = queryset.filter(status__in=['draft', 'testing', 'staged']).update( - status='released', - released_at=timezone.now() - ) - self.message_user(request, f"Released {count} version(s)") + count = 0 + for version in queryset.filter(status__in=['draft', 'testing', 'staged']): + version.status = 'released' + version.save() # Triggers signal to build ZIP + count += 1 + self.message_user(request, f"Released {count} version(s). ZIP files are being built automatically.") - @admin.action(description="Mark as update ready (push to installations)") + @admin.action(description="📢 Mark as update ready (notify WordPress sites)") def mark_as_update_ready(self, request, queryset): count = queryset.filter(status='released').update(status='update_ready') - self.message_user(request, f"Marked {count} version(s) as update ready") + self.message_user(request, f"Marked {count} version(s) as update ready. WordPress sites will be notified.") + + @admin.action(description="🗑️ Mark as deprecated") + def mark_as_deprecated(self, request, queryset): + count = queryset.update(status='deprecated') + self.message_user(request, f"Marked {count} version(s) as deprecated") @admin.register(PluginInstallation) diff --git a/backend/igny8_core/plugins/migrations/0002_allow_blank_autogenerated_fields.py b/backend/igny8_core/plugins/migrations/0002_allow_blank_autogenerated_fields.py new file mode 100644 index 00000000..bb10a0e8 --- /dev/null +++ b/backend/igny8_core/plugins/migrations/0002_allow_blank_autogenerated_fields.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.10 on 2026-01-09 23:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plugins', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='pluginversion', + name='file_path', + field=models.CharField(blank=True, default='', help_text='Relative path to ZIP file in dist/ directory. Auto-generated on release.', max_length=500), + ), + migrations.AlterField( + model_name='pluginversion', + name='version_code', + field=models.IntegerField(blank=True, help_text='Numeric version for comparison (1.0.1 -> 10001). Auto-calculated.', null=True), + ), + ] diff --git a/backend/igny8_core/plugins/models.py b/backend/igny8_core/plugins/models.py index 1bde6549..271cd7f2 100644 --- a/backend/igny8_core/plugins/models.py +++ b/backend/igny8_core/plugins/models.py @@ -99,7 +99,9 @@ class PluginVersion(models.Model): help_text="Semantic version string (e.g., '1.0.0', '1.0.1')" ) version_code = models.IntegerField( - help_text="Numeric version for comparison (1.0.1 -> 10001)" + null=True, + blank=True, + help_text="Numeric version for comparison (1.0.1 -> 10001). Auto-calculated." ) status = models.CharField( max_length=20, @@ -112,7 +114,9 @@ class PluginVersion(models.Model): # File info file_path = models.CharField( max_length=500, - help_text="Relative path to ZIP file in dist/ directory" + blank=True, + default='', + help_text="Relative path to ZIP file in dist/ directory. Auto-generated on release." ) file_size = models.IntegerField( default=0, diff --git a/backend/igny8_core/plugins/signals.py b/backend/igny8_core/plugins/signals.py index a2f785b2..83a25b68 100644 --- a/backend/igny8_core/plugins/signals.py +++ b/backend/igny8_core/plugins/signals.py @@ -23,58 +23,68 @@ def auto_build_plugin_on_release(sender, instance, **kwargs): 1. ZIP file is always up-to-date with source code 2. File size and checksum are auto-calculated 3. No manual intervention needed for releases + + Triggers on: + - New version created with status 'released' or 'update_ready' + - Existing version status changed to 'released' or 'update_ready' """ - # Skip if this is a new instance (no pk yet) - if not instance.pk: - return - - try: - # Get the old instance to check for status change - old_instance = PluginVersion.objects.get(pk=instance.pk) - except PluginVersion.DoesNotExist: - return - - # Check if status is changing TO 'released' or 'update_ready' release_statuses = ['released', 'update_ready'] - old_status = old_instance.status - new_status = instance.status - # Only trigger build if: - # 1. Status is changing - # 2. New status is a release status - # 3. Old status was not a release status (avoid rebuilding on every save) - if old_status == new_status: + # Check if this version should have a ZIP built + should_build = False + + if not instance.pk: + # New instance - build if status is a release status + if instance.status in release_statuses: + should_build = True + logger.info(f"New plugin version {instance.plugin.slug} v{instance.version} created with status '{instance.status}' - building ZIP") + else: + # Existing instance - check if status changed to a release status + try: + old_instance = PluginVersion.objects.get(pk=instance.pk) + old_status = old_instance.status + new_status = instance.status + + # Build if moving to a release status from a non-release status + if new_status in release_statuses and old_status not in release_statuses: + should_build = True + logger.info(f"Building plugin ZIP for {instance.plugin.slug} v{instance.version} (status: {old_status} -> {new_status})") + elif old_status == new_status and new_status in release_statuses: + # No status change, but already released - no rebuild + return + elif old_status in release_statuses and new_status in release_statuses: + # Moving between release statuses - no rebuild + logger.info(f"Plugin {instance.plugin.slug} v{instance.version}: Status changing from {old_status} to {new_status}, no rebuild needed") + return + except PluginVersion.DoesNotExist: + return + + if not should_build: return - if new_status not in release_statuses: - return - - # If moving from one release status to another, don't rebuild - if old_status in release_statuses: - logger.info(f"Plugin {instance.plugin.slug} v{instance.version}: Status changing from {old_status} to {new_status}, no rebuild needed") - return - - logger.info(f"Building plugin ZIP for {instance.plugin.slug} v{instance.version} (status: {old_status} -> {new_status})") - # Build the ZIP - file_path, checksum, file_size = create_plugin_zip( - platform=instance.plugin.platform, - plugin_slug=instance.plugin.slug, - version=instance.version, - update_version=True - ) - - if not file_path: - logger.error(f"Failed to build ZIP for {instance.plugin.slug} v{instance.version}") - return - - # Update the instance with new file info - instance.file_path = file_path - instance.checksum = checksum - instance.file_size = file_size - - # Set released_at if moving to released status and not already set - if new_status in release_statuses and not instance.released_at: - instance.released_at = timezone.now() - - logger.info(f"Built plugin ZIP: {file_path} ({file_size} bytes, checksum: {checksum[:16]}...)") + try: + file_path, checksum, file_size = create_plugin_zip( + platform=instance.plugin.platform, + plugin_slug=instance.plugin.slug, + version=instance.version, + update_version=True + ) + + if not file_path: + logger.error(f"Failed to build ZIP for {instance.plugin.slug} v{instance.version}") + return + + # Update the instance with new file info + instance.file_path = file_path + instance.checksum = checksum + instance.file_size = file_size + + # Set released_at if not already set + if not instance.released_at: + instance.released_at = timezone.now() + + logger.info(f"Built plugin ZIP: {file_path} ({file_size} bytes, checksum: {checksum[:16]}...)") + + except Exception as e: + logger.error(f"Error building ZIP for {instance.plugin.slug} v{instance.version}: {e}") diff --git a/docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md b/docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md index 21bd22b8..390a572d 100644 --- a/docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md +++ b/docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md @@ -6,6 +6,26 @@ --- +## 🎯 Quick Start: Simplified Release Process + +The plugin release process has been **simplified** to require only 3 fields: + +1. **Version** (e.g., 1.2.0) +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' +``` + +--- + ## Table of Contents 1. [Update Workflow Overview](#1-update-workflow-overview) @@ -128,11 +148,12 @@ WordPress will show "Update Available" in: | Action | When | How | |--------|------|-----| | **Update Source Version** | Before release | Edit `igny8-bridge.php` header | -| **Create PluginVersion Record** | Each release | Django admin or management command | -| **Write Changelog** | Each release | Enter in PluginVersion.changelog | +| **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: @@ -154,6 +175,37 @@ define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← AND THIS ## 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 @@ -181,21 +233,35 @@ define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← Match here 1. Go to: `https://api.igny8.com/backend/` 2. Navigate to: **Plugin Distribution** → **Plugin Versions** 3. Click **Add Plugin Version** -4. Fill in: +4. Fill in **ONLY** these required fields: - **Plugin:** IGNY8 WordPress Bridge - **Version:** 1.2.0 - **Status:** draft (initially) - **Changelog:** Describe changes - - **Min PHP Version:** 7.4 (or higher if needed) 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** -**What happens:** Signal triggers auto-build → ZIP created → database updated +**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 @@ -211,32 +277,127 @@ Run the [verification checklist](#5-post-update-verification-checklist) below. ## 5. Post-Update Verification Checklist -### Immediate Checks (Do Every Release) +### Quick Verification Script + +Run this single command to verify everything: ```bash -# 1. Verify ZIP exists -ls -lh /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip +#!/bin/bash +# Complete Plugin Release Verification Script +# Usage: Save as verify-plugin.sh and run: bash verify-plugin.sh 1.1.2 -# 2. Verify symlink updated -ls -la /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-latest.zip +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 -curl -s -o /dev/null -w "%{http_code} - %{size_download} bytes\n" \ - https://api.igny8.com/api/plugins/igny8-wp-bridge/download/ +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 -curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0" | python3 -m json.tool +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 -curl -s https://api.igny8.com/api/plugins/igny8-wp-bridge/info/ | python3 -m json.tool +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 -unzip -p /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip \ - igny8-wp-bridge/igny8-bridge.php | grep -E "Version:|IGNY8_BRIDGE_VERSION" +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 in ZIP (must be api.igny8.com) -unzip -p /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip \ - igny8-wp-bridge/igny8-bridge.php | grep "api.igny8.com" +# 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 @@ -382,17 +543,27 @@ curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_ver ## Quick Reference Card -### Release New Version +### Release New Version (Simplified) ``` -1. Edit source code +1. Edit source code in /plugins/wordpress/source/igny8-wp-bridge/ 2. Update version in igny8-bridge.php (header + constant) -3. Create PluginVersion in Django admin (status: draft) -4. Change status to "released" → Auto-build triggers +3. Django Admin → Add Plugin Version: + - Plugin: IGNY8 WordPress Bridge + - Version: 1.2.0 + - 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. Deprecate old version +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 diff --git a/docs/60-PLUGINS/WORDPRESS-INTEGRATION.md b/docs/60-PLUGINS/WORDPRESS-INTEGRATION.md index a1f69e16..c4c52393 100644 --- a/docs/60-PLUGINS/WORDPRESS-INTEGRATION.md +++ b/docs/60-PLUGINS/WORDPRESS-INTEGRATION.md @@ -390,23 +390,32 @@ View and manage plugin registry. Manage versions and trigger releases. -| Field | Description | -|-------|-------------| -| Plugin | Parent plugin | -| Version | Semantic version (e.g., 1.1.1) | -| Status | draft, testing, staged, released, update_ready, deprecated | -| File Path | Auto-set on release | -| File Size | Auto-calculated on release | -| Checksum | Auto-calculated on release | -| Changelog | What's new (shown to users) | -| Force Update | Set true for critical security fixes | +**Required Fields Only:** +- Plugin +- Version (e.g., 1.2.0) +- Changelog +- Status + +**Auto-Filled Fields:** +- Min API/Platform/PHP versions (copied from previous version) +- File path, size, checksum (generated on release) +- Version code (calculated from version number) **To Release a New Version:** -1. Create new PluginVersion record with status `draft` -2. Enter version number, changelog -3. Change status to `released` or `update_ready` -4. Save → ZIP is automatically built +1. Click **Add Plugin Version** +2. Select plugin +3. Enter version number (e.g., 1.2.0) +4. Write changelog +5. Set status to `draft` +6. Save +7. Change status to `released` (or use bulk action "✅ Release selected versions") +8. ZIP is automatically built with all file info populated + +**Bulk Actions:** +- **✅ Release selected versions** - Builds ZIP and releases +- **📢 Mark as update ready** - Notifies WordPress sites +- **🗑️ Mark as deprecated** - Deprecates old versions #### Plugin Installations diff --git a/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php b/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php index 513a385c..624d0164 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php +++ b/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php @@ -3,7 +3,7 @@ * Plugin Name: IGNY8 WordPress Bridge * Plugin URI: https://igny8.com/igny8-wp-bridge * Description: Lightweight bridge plugin that connects WordPress to IGNY8 API for one-way content publishing. - * Version: 1.1.1 + * Version: 1.1.2 * Author: IGNY8 * Author URI: https://igny8.com/ * License: GPL v2 or later @@ -167,6 +167,44 @@ class Igny8Bridge { if (!get_option('igny8_default_post_status')) { add_option('igny8_default_post_status', 'draft'); } + + // Register installation with IGNY8 API + $this->register_installation(); + } + + /** + * Register this plugin installation with IGNY8 API + */ + private function register_installation() { + // Get API key and site ID + $api_key = get_option('igny8_api_key'); + $site_id = get_option('igny8_site_id'); + + // Skip if not configured yet + if (empty($api_key) || empty($site_id)) { + return; + } + + // Register installation + $response = wp_remote_post( + 'https://api.igny8.com/api/plugins/igny8-wp-bridge/register-installation/', + array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $api_key, + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode(array( + 'site_id' => intval($site_id), + 'version' => IGNY8_BRIDGE_VERSION, + )), + 'timeout' => 15, + ) + ); + + // Log result (don't fail activation if this fails) + if (is_wp_error($response)) { + error_log('IGNY8 Bridge: Failed to register installation - ' . $response->get_error_message()); + } } /**