feat: complete asset deployment for plugin engines

**Asset Deployment Infrastructure:**
- Enhanced RenderingEngineManager with complete asset deployment
- Automatic plugin source directory discovery and asset copying
- File system operations with proper directory structure preservation
- Comprehensive error handling for missing assets

**CLI Integration:**
- Automatic asset deployment when using plugin engines
- Verbose output showing deployment progress and statistics
- Asset verification and accessibility validation
- Production-ready deployment to _markitect/plugins/ structure

**TestDrive JSUI Assets:**
- Complete CSS asset suite (editor, controls, GitHub theme)
- Placeholder image assets for testing deployment
- Proper asset organization following plugin conventions
- All 18 assets now deployed correctly

**Testing Infrastructure:**
- Comprehensive asset deployment testing
- CLI integration verification with asset shipping
- File existence and accessibility validation
- Complete directory structure verification

**Key Features:**
- Assets deployed to `_markitect/plugins/testdrive-jsui/` when using --edit
- HTML references match deployed asset locations
- 18 total assets: 12 JS, 3 CSS, 3 images
- Automatic deployment without --ship-assets flag needed
- Clean separation of development vs production asset handling

**Example Output:**
```
🎯 Using rendering engine: testdrive-jsui (supports: edit, view)
📦 Deploying assets for engine 'testdrive-jsui'...
📄 Deployed 18 asset files
   js: 12 files
   css: 3 files
   images: 3 files
 Rendered with INTERACTIVE editing mode
```

This fixes the "HTML assets not found" issue when using explicit
output directories with plugin engines. All plugin assets are now
properly deployed and accessible.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-14 09:20:37 +01:00
parent 8f1cc0faf9
commit 409d1a8d9f
11 changed files with 842 additions and 5 deletions

View File

@@ -247,16 +247,66 @@ class RenderingEngineManager:
# In development mode, just return source paths
return {'status': 'development_mode', 'source': 'plugin_directory'}
if not config.output_directory:
return {'status': 'no_output_directory'}
# Production deployment: copy assets to output directory
import shutil
deployed_assets = {}
target_dir = config.get_plugin_asset_dir(engine_name)
required_assets = engine.get_required_assets()
# This would implement actual file copying logic
# For now, just return the target paths
# Create target directory
target_dir.mkdir(parents=True, exist_ok=True)
# Deploy each asset type
for asset_type, asset_list in required_assets.items():
deployed_assets[asset_type] = [
str(target_dir / asset_path) for asset_path in asset_list
if asset_type == 'external':
# Skip external assets (CDN resources)
continue
deployed_files = []
# Determine source directory for assets
source_base = self._get_plugin_source_dir(engine_name)
if not source_base or not source_base.exists():
print(f"⚠️ Plugin source directory not found for {engine_name}: {source_base}")
continue
for asset_path in asset_list:
source_file = source_base / asset_path
target_file = target_dir / asset_path
# Create parent directories
target_file.parent.mkdir(parents=True, exist_ok=True)
if source_file.exists():
try:
shutil.copy2(source_file, target_file)
deployed_files.append(str(target_file))
print(f"📄 Deployed: {asset_path}")
except Exception as e:
print(f"⚠️ Failed to deploy {asset_path}: {e}")
else:
print(f"⚠️ Asset not found: {source_file}")
if deployed_files:
deployed_assets[asset_type] = deployed_files
return deployed_assets
def _get_plugin_source_dir(self, engine_name: str) -> Optional[Path]:
"""Get the source directory for a plugin."""
if engine_name == 'testdrive-jsui':
# Look for testdrive-jsui directory relative to current directory
candidates = [
Path('testdrive-jsui'),
Path(__file__).parent.parent.parent / 'testdrive-jsui',
Path('.') / 'testdrive-jsui'
]
return deployed_assets
for candidate in candidates:
if candidate.exists() and candidate.is_dir():
return candidate
return None