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

@@ -2230,6 +2230,19 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, engine,
# Render using plugin
html_content = rendering_engine.render_document(content, 'edit', render_config)
# Deploy plugin assets
if not silent:
click.echo(f"📦 Deploying assets for engine '{engine}'...")
deployed_assets = rendering_manager.deploy_engine_assets(engine, render_config)
if verbose and deployed_assets:
total_assets = sum(len(files) for files in deployed_assets.values() if isinstance(files, list))
click.echo(f" 📄 Deployed {total_assets} asset files")
for asset_type, files in deployed_assets.items():
if isinstance(files, list) and files:
click.echo(f" {asset_type}: {len(files)} files")
# Write output
output_path.write_text(html_content, encoding='utf-8')
result = True
@@ -2283,6 +2296,19 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, engine,
if not silent:
click.echo(f" Engine '{engine}' doesn't support insert mode, using edit mode instead")
# Deploy plugin assets
if not silent:
click.echo(f"📦 Deploying assets for engine '{engine}'...")
deployed_assets = rendering_manager.deploy_engine_assets(engine, render_config)
if verbose and deployed_assets:
total_assets = sum(len(files) for files in deployed_assets.values() if isinstance(files, list))
click.echo(f" 📄 Deployed {total_assets} asset files")
for asset_type, files in deployed_assets.items():
if isinstance(files, list) and files:
click.echo(f" {asset_type}: {len(files)} files")
# Write output
output_path.write_text(html_content, encoding='utf-8')
result = True

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