PLugin versioning fixes
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Django Admin Configuration for Plugin Distribution System
|
Django Admin Configuration for Plugin Distribution System
|
||||||
"""
|
"""
|
||||||
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -8,6 +9,68 @@ from unfold.admin import ModelAdmin, TabularInline
|
|||||||
from .models import Plugin, PluginVersion, PluginInstallation, PluginDownload
|
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):
|
class PluginVersionInline(TabularInline):
|
||||||
"""Inline admin for plugin versions."""
|
"""Inline admin for plugin versions."""
|
||||||
model = PluginVersion
|
model = PluginVersion
|
||||||
@@ -66,35 +129,37 @@ class PluginAdmin(ModelAdmin):
|
|||||||
class PluginVersionAdmin(ModelAdmin):
|
class PluginVersionAdmin(ModelAdmin):
|
||||||
"""Admin configuration for PluginVersion model."""
|
"""Admin configuration for PluginVersion model."""
|
||||||
|
|
||||||
|
form = PluginVersionForm
|
||||||
|
|
||||||
list_display = ['plugin', 'version', 'status', 'file_size_display', 'download_count', 'released_at']
|
list_display = ['plugin', 'version', 'status', 'file_size_display', 'download_count', 'released_at']
|
||||||
list_filter = ['plugin', 'status', 'released_at']
|
list_filter = ['plugin', 'status', 'released_at']
|
||||||
search_fields = ['plugin__name', 'version', 'changelog']
|
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 = [
|
fieldsets = [
|
||||||
('Version Info', {
|
('Required Fields', {
|
||||||
'fields': ['plugin', 'version', 'version_code', 'status']
|
'fields': ['plugin', 'version', 'status', 'changelog'],
|
||||||
|
'description': 'Only these fields are required. Others are auto-filled from previous version or auto-generated.'
|
||||||
}),
|
}),
|
||||||
('File Info', {
|
('Requirements (Auto-filled from previous version)', {
|
||||||
'fields': ['file_path', 'file_size', 'checksum']
|
'fields': ['min_api_version', 'min_platform_version', 'min_php_version'],
|
||||||
}),
|
|
||||||
('Requirements', {
|
|
||||||
'fields': ['min_api_version', 'min_platform_version', 'min_php_version']
|
|
||||||
}),
|
|
||||||
('Release', {
|
|
||||||
'fields': ['changelog', 'force_update', 'released_at']
|
|
||||||
}),
|
|
||||||
('Statistics', {
|
|
||||||
'fields': ['download_count'],
|
|
||||||
'classes': ['collapse']
|
'classes': ['collapse']
|
||||||
}),
|
}),
|
||||||
('Timestamps', {
|
('File Info (Auto-generated on release)', {
|
||||||
'fields': ['created_at'],
|
'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']
|
'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):
|
def file_size_display(self, obj):
|
||||||
if obj.file_size:
|
if obj.file_size:
|
||||||
@@ -109,19 +174,25 @@ class PluginVersionAdmin(ModelAdmin):
|
|||||||
return obj.get_download_count()
|
return obj.get_download_count()
|
||||||
download_count.short_description = 'Downloads'
|
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):
|
def release_versions(self, request, queryset):
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
count = queryset.filter(status__in=['draft', 'testing', 'staged']).update(
|
count = 0
|
||||||
status='released',
|
for version in queryset.filter(status__in=['draft', 'testing', 'staged']):
|
||||||
released_at=timezone.now()
|
version.status = 'released'
|
||||||
)
|
version.save() # Triggers signal to build ZIP
|
||||||
self.message_user(request, f"Released {count} version(s)")
|
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):
|
def mark_as_update_ready(self, request, queryset):
|
||||||
count = queryset.filter(status='released').update(status='update_ready')
|
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)
|
@admin.register(PluginInstallation)
|
||||||
|
|||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -99,7 +99,9 @@ class PluginVersion(models.Model):
|
|||||||
help_text="Semantic version string (e.g., '1.0.0', '1.0.1')"
|
help_text="Semantic version string (e.g., '1.0.0', '1.0.1')"
|
||||||
)
|
)
|
||||||
version_code = models.IntegerField(
|
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(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
@@ -112,7 +114,9 @@ class PluginVersion(models.Model):
|
|||||||
# File info
|
# File info
|
||||||
file_path = models.CharField(
|
file_path = models.CharField(
|
||||||
max_length=500,
|
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(
|
file_size = models.IntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
|
|||||||
@@ -23,58 +23,68 @@ def auto_build_plugin_on_release(sender, instance, **kwargs):
|
|||||||
1. ZIP file is always up-to-date with source code
|
1. ZIP file is always up-to-date with source code
|
||||||
2. File size and checksum are auto-calculated
|
2. File size and checksum are auto-calculated
|
||||||
3. No manual intervention needed for releases
|
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']
|
release_statuses = ['released', 'update_ready']
|
||||||
old_status = old_instance.status
|
|
||||||
new_status = instance.status
|
|
||||||
|
|
||||||
# Only trigger build if:
|
# Check if this version should have a ZIP built
|
||||||
# 1. Status is changing
|
should_build = False
|
||||||
# 2. New status is a release status
|
|
||||||
# 3. Old status was not a release status (avoid rebuilding on every save)
|
if not instance.pk:
|
||||||
if old_status == new_status:
|
# 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
|
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
|
# Build the ZIP
|
||||||
file_path, checksum, file_size = create_plugin_zip(
|
try:
|
||||||
platform=instance.plugin.platform,
|
file_path, checksum, file_size = create_plugin_zip(
|
||||||
plugin_slug=instance.plugin.slug,
|
platform=instance.plugin.platform,
|
||||||
version=instance.version,
|
plugin_slug=instance.plugin.slug,
|
||||||
update_version=True
|
version=instance.version,
|
||||||
)
|
update_version=True
|
||||||
|
)
|
||||||
if not file_path:
|
|
||||||
logger.error(f"Failed to build ZIP for {instance.plugin.slug} v{instance.version}")
|
if not file_path:
|
||||||
return
|
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
|
# Update the instance with new file info
|
||||||
instance.checksum = checksum
|
instance.file_path = file_path
|
||||||
instance.file_size = file_size
|
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:
|
# Set released_at if not already set
|
||||||
instance.released_at = timezone.now()
|
if not instance.released_at:
|
||||||
|
instance.released_at = timezone.now()
|
||||||
logger.info(f"Built plugin ZIP: {file_path} ({file_size} bytes, checksum: {checksum[:16]}...)")
|
|
||||||
|
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}")
|
||||||
|
|||||||
@@ -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
|
## Table of Contents
|
||||||
|
|
||||||
1. [Update Workflow Overview](#1-update-workflow-overview)
|
1. [Update Workflow Overview](#1-update-workflow-overview)
|
||||||
@@ -128,11 +148,12 @@ WordPress will show "Update Available" in:
|
|||||||
| Action | When | How |
|
| Action | When | How |
|
||||||
|--------|------|-----|
|
|--------|------|-----|
|
||||||
| **Update Source Version** | Before release | Edit `igny8-bridge.php` header |
|
| **Update Source Version** | Before release | Edit `igny8-bridge.php` header |
|
||||||
| **Create PluginVersion Record** | Each release | Django admin or management command |
|
| **Create PluginVersion Record** | Each release | Django admin: just enter version, changelog, status |
|
||||||
| **Write Changelog** | Each release | Enter in PluginVersion.changelog |
|
|
||||||
| **Verify After Release** | After status change | Run verification checklist |
|
| **Verify After Release** | After status change | Run verification checklist |
|
||||||
| **Mark Old Version Deprecated** | After successful release | Change old version status |
|
| **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
|
### Source Version Update Locations
|
||||||
|
|
||||||
When releasing a new version, update these in the source:
|
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
|
## 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
|
### Step 1: Make Code Changes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -181,21 +233,35 @@ define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← Match here
|
|||||||
1. Go to: `https://api.igny8.com/backend/`
|
1. Go to: `https://api.igny8.com/backend/`
|
||||||
2. Navigate to: **Plugin Distribution** → **Plugin Versions**
|
2. Navigate to: **Plugin Distribution** → **Plugin Versions**
|
||||||
3. Click **Add Plugin Version**
|
3. Click **Add Plugin Version**
|
||||||
4. Fill in:
|
4. Fill in **ONLY** these required fields:
|
||||||
- **Plugin:** IGNY8 WordPress Bridge
|
- **Plugin:** IGNY8 WordPress Bridge
|
||||||
- **Version:** 1.2.0
|
- **Version:** 1.2.0
|
||||||
- **Status:** draft (initially)
|
- **Status:** draft (initially)
|
||||||
- **Changelog:** Describe changes
|
- **Changelog:** Describe changes
|
||||||
- **Min PHP Version:** 7.4 (or higher if needed)
|
|
||||||
5. Click **Save**
|
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
|
### Step 4: Release the Version
|
||||||
|
|
||||||
|
**Option A: Release via Status Change**
|
||||||
|
|
||||||
1. Edit the PluginVersion you just created
|
1. Edit the PluginVersion you just created
|
||||||
2. Change **Status** to `released`
|
2. Change **Status** to `released`
|
||||||
3. Click **Save**
|
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
|
### Step 5: Verify Release
|
||||||
|
|
||||||
@@ -211,32 +277,127 @@ Run the [verification checklist](#5-post-update-verification-checklist) below.
|
|||||||
|
|
||||||
## 5. Post-Update Verification Checklist
|
## 5. Post-Update Verification Checklist
|
||||||
|
|
||||||
### Immediate Checks (Do Every Release)
|
### Quick Verification Script
|
||||||
|
|
||||||
|
Run this single command to verify everything:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Verify ZIP exists
|
#!/bin/bash
|
||||||
ls -lh /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip
|
# Complete Plugin Release Verification Script
|
||||||
|
# Usage: Save as verify-plugin.sh and run: bash verify-plugin.sh 1.1.2
|
||||||
|
|
||||||
# 2. Verify symlink updated
|
VERSION=${1:-"latest"}
|
||||||
ls -la /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-latest.zip
|
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
|
# 3. Test download endpoint
|
||||||
curl -s -o /dev/null -w "%{http_code} - %{size_download} bytes\n" \
|
echo "3. Testing download endpoint..."
|
||||||
https://api.igny8.com/api/plugins/igny8-wp-bridge/download/
|
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
|
# 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
|
# 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
|
# 6. Verify version in ZIP
|
||||||
unzip -p /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip \
|
echo "6. Verifying version in ZIP..."
|
||||||
igny8-wp-bridge/igny8-bridge.php | grep -E "Version:|IGNY8_BRIDGE_VERSION"
|
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)
|
# 7. Verify API URL
|
||||||
unzip -p /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip \
|
echo "7. Verifying API URL in ZIP..."
|
||||||
igny8-wp-bridge/igny8-bridge.php | grep "api.igny8.com"
|
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
|
### Database Verification
|
||||||
@@ -382,17 +543,27 @@ curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_ver
|
|||||||
|
|
||||||
## Quick Reference Card
|
## 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)
|
2. Update version in igny8-bridge.php (header + constant)
|
||||||
3. Create PluginVersion in Django admin (status: draft)
|
3. Django Admin → Add Plugin Version:
|
||||||
4. Change status to "released" → Auto-build triggers
|
- 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
|
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
|
### Verification Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -390,23 +390,32 @@ View and manage plugin registry.
|
|||||||
|
|
||||||
Manage versions and trigger releases.
|
Manage versions and trigger releases.
|
||||||
|
|
||||||
| Field | Description |
|
**Required Fields Only:**
|
||||||
|-------|-------------|
|
- Plugin
|
||||||
| Plugin | Parent plugin |
|
- Version (e.g., 1.2.0)
|
||||||
| Version | Semantic version (e.g., 1.1.1) |
|
- Changelog
|
||||||
| Status | draft, testing, staged, released, update_ready, deprecated |
|
- Status
|
||||||
| File Path | Auto-set on release |
|
|
||||||
| File Size | Auto-calculated on release |
|
**Auto-Filled Fields:**
|
||||||
| Checksum | Auto-calculated on release |
|
- Min API/Platform/PHP versions (copied from previous version)
|
||||||
| Changelog | What's new (shown to users) |
|
- File path, size, checksum (generated on release)
|
||||||
| Force Update | Set true for critical security fixes |
|
- Version code (calculated from version number)
|
||||||
|
|
||||||
**To Release a New Version:**
|
**To Release a New Version:**
|
||||||
|
|
||||||
1. Create new PluginVersion record with status `draft`
|
1. Click **Add Plugin Version**
|
||||||
2. Enter version number, changelog
|
2. Select plugin
|
||||||
3. Change status to `released` or `update_ready`
|
3. Enter version number (e.g., 1.2.0)
|
||||||
4. Save → ZIP is automatically built
|
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
|
#### Plugin Installations
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: IGNY8 WordPress Bridge
|
* Plugin Name: IGNY8 WordPress Bridge
|
||||||
* Plugin URI: https://igny8.com/igny8-wp-bridge
|
* Plugin URI: https://igny8.com/igny8-wp-bridge
|
||||||
* Description: Lightweight bridge plugin that connects WordPress to IGNY8 API for one-way content publishing.
|
* 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: IGNY8
|
||||||
* Author URI: https://igny8.com/
|
* Author URI: https://igny8.com/
|
||||||
* License: GPL v2 or later
|
* License: GPL v2 or later
|
||||||
@@ -167,6 +167,44 @@ class Igny8Bridge {
|
|||||||
if (!get_option('igny8_default_post_status')) {
|
if (!get_option('igny8_default_post_status')) {
|
||||||
add_option('igny8_default_post_status', 'draft');
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user