mirror of
https://github.com/ONLYOFFICE/onlyoffice.github.io.git
synced 2026-02-10 18:05:06 +08:00
244 lines
8.9 KiB
Python
244 lines
8.9 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
Plugin Packer - Utility for packaging plugins with configurable exclusions
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import json
|
|
import time
|
|
import re
|
|
from pathlib import Path
|
|
from fnmatch import fnmatch
|
|
import argparse
|
|
|
|
|
|
class PluginPacker:
|
|
"""Main class for packing plugins with configurable exclusion patterns"""
|
|
|
|
DEFAULT_EXCLUDES = ['deploy/*', 'node_modules/*', '.dev/*']
|
|
MAX_RETRIES = 3
|
|
RETRY_DELAY = 1
|
|
OLD_PATH_PATTERN = r'https://onlyoffice\.github\.io/sdkjs-plugins/v1/[\w\-\./\*]*'
|
|
NEW_PATH = './../v1/'
|
|
|
|
def __init__(self, old_mode=False):
|
|
self.old_mode = old_mode
|
|
self.content_dir = "sdkjs-plugins/content/"
|
|
self.artifacts_dir = "artifacts"
|
|
|
|
def parse_arguments(self):
|
|
"""Parse command line arguments"""
|
|
parser = argparse.ArgumentParser(description='Pack plugins')
|
|
parser.add_argument(
|
|
'--old-mode',
|
|
action='store_true',
|
|
help='The old way: put the result in the root of each plugin\'s folder.'
|
|
)
|
|
return parser.parse_args()
|
|
|
|
def replace_html_paths(self, file_path):
|
|
"""Replace paths in HTML files before packing"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
new_content = re.sub(self.OLD_PATH_PATTERN, lambda match: self.NEW_PATH + match.group(0).split('/v1/')[1], content)
|
|
|
|
# If changes were made, write back to file
|
|
if new_content != content:
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
return True
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f" Error processing HTML file {file_path}: {e}")
|
|
return False
|
|
|
|
def process_html_files(self, directory):
|
|
"""Find and process all HTML files in directory"""
|
|
html_files_processed = 0
|
|
for root, dirs, files in os.walk(directory):
|
|
for file in files:
|
|
if file.lower().endswith('.html'):
|
|
file_path = os.path.join(root, file)
|
|
if self.replace_html_paths(file_path):
|
|
html_files_processed += 1
|
|
return html_files_processed
|
|
|
|
def safe_rename(self, src, dst, max_retries=MAX_RETRIES, delay=RETRY_DELAY):
|
|
"""Safely rename file with retry logic for locked files"""
|
|
for attempt in range(max_retries):
|
|
try:
|
|
Path(src).rename(dst)
|
|
return True
|
|
except PermissionError:
|
|
if attempt < max_retries - 1:
|
|
print(f" File busy, retrying in {delay} second(s)...")
|
|
time.sleep(delay)
|
|
else:
|
|
print(f" Failed to rename after {max_retries} attempts")
|
|
return False
|
|
return False
|
|
|
|
def delete_dir(self, path, max_retries=MAX_RETRIES, delay=RETRY_DELAY):
|
|
"""Safely delete directory with retry logic"""
|
|
if not os.path.exists(path):
|
|
return True
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
shutil.rmtree(path)
|
|
return True
|
|
except PermissionError:
|
|
if attempt < max_retries - 1:
|
|
print(f"Attempt {attempt + 1}: File in use... {path}")
|
|
time.sleep(delay)
|
|
else:
|
|
print(f"Failed to delete {path}")
|
|
return False
|
|
return False
|
|
|
|
def get_plugin_excludes(self, plugin_path):
|
|
"""Get exclusion patterns for plugin from config.json"""
|
|
plugin_config_path = os.path.join(plugin_path, ".dev", "config.json")
|
|
|
|
excludes_set = set(self.DEFAULT_EXCLUDES)
|
|
|
|
if os.path.exists(plugin_config_path):
|
|
try:
|
|
with open(plugin_config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
custom_excludes = config.get("excludes", [])
|
|
excludes_set.update(custom_excludes)
|
|
except (json.JSONDecodeError, Exception) as e:
|
|
print(f"[{os.path.basename(plugin_path)}] Error reading config: {e}")
|
|
|
|
return list(excludes_set)
|
|
|
|
def should_exclude(self, path, base_dir, excludes):
|
|
"""Check if path should be excluded based on patterns"""
|
|
if not excludes:
|
|
return False
|
|
|
|
# Convert path to relative format
|
|
if os.path.isabs(path):
|
|
relative_path = os.path.relpath(path, base_dir)
|
|
else:
|
|
relative_path = path
|
|
|
|
# Normalize path separators
|
|
relative_path = relative_path.replace('\\', '/')
|
|
|
|
# Check all exclusion patterns
|
|
for pattern in excludes:
|
|
pattern = pattern.replace('\\', '/')
|
|
if (fnmatch(relative_path, pattern) or
|
|
fnmatch(relative_path + '/', pattern + '/')):
|
|
return True
|
|
|
|
return False
|
|
|
|
def copy_filtered_files(self, source_dir, dest_dir, excludes):
|
|
"""Copy files with exclusion patterns applied"""
|
|
for root, dirs, files in os.walk(source_dir):
|
|
# Filter directories during traversal
|
|
dirs[:] = [d for d in dirs if not self.should_exclude(
|
|
os.path.join(root, d), source_dir, excludes)]
|
|
|
|
for file in files:
|
|
source_file = os.path.join(root, file)
|
|
relative_path = os.path.relpath(source_file, source_dir)
|
|
|
|
if not self.should_exclude(relative_path, source_dir, excludes):
|
|
dest_file = os.path.join(dest_dir, relative_path)
|
|
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
|
|
shutil.copy2(source_file, dest_file)
|
|
|
|
def create_plugin_archive(self, source_dir, plugin_name, output_dir, excludes):
|
|
"""Create .plugin archive from source directory"""
|
|
temp_dir = os.path.join(output_dir, f"temp_{plugin_name}")
|
|
os.makedirs(temp_dir, exist_ok=True)
|
|
|
|
try:
|
|
# Copy filtered files to temp directory
|
|
self.copy_filtered_files(source_dir, temp_dir, excludes or [])
|
|
|
|
# Check if any files remain
|
|
if not any(os.scandir(temp_dir)):
|
|
print(f"[{plugin_name}] No files to pack after filtering")
|
|
return False
|
|
|
|
# Process HTML files in temp directory (before archiving)
|
|
self.process_html_files(temp_dir)
|
|
|
|
|
|
# Create and rename archive
|
|
zip_file = os.path.join(output_dir, plugin_name)
|
|
zip_path = shutil.make_archive(zip_file, 'zip', temp_dir)
|
|
plugin_file_path = Path(zip_path).with_suffix(".plugin")
|
|
|
|
if self.safe_rename(zip_path, plugin_file_path):
|
|
print(f"✅ Created: {plugin_name}")
|
|
return True
|
|
else:
|
|
print(f"❌ Failed to create: {plugin_name}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"[{plugin_name}] Error: {e}")
|
|
return False
|
|
finally:
|
|
self.delete_dir(temp_dir)
|
|
|
|
def pack_plugin_new_mode(self, plugin_path, plugin_name):
|
|
"""Pack plugin in new mode (artifacts directory)"""
|
|
excludes = self.get_plugin_excludes(plugin_path)
|
|
return self.create_plugin_archive(plugin_path, plugin_name, self.artifacts_dir, excludes)
|
|
|
|
def pack_plugin_old_mode(self, plugin_path, plugin_name):
|
|
"""Pack plugin in old mode (plugin's deploy directory)"""
|
|
excludes = self.get_plugin_excludes(plugin_path)
|
|
destination_path = os.path.join(plugin_path, "deploy")
|
|
|
|
# Clean up old deploy folder
|
|
if os.path.exists(destination_path):
|
|
self.delete_dir(destination_path)
|
|
|
|
return self.create_plugin_archive(plugin_path, plugin_name, destination_path, excludes)
|
|
|
|
def pack_plugins(self):
|
|
"""Main packing method"""
|
|
if not os.path.exists(self.content_dir):
|
|
print(f"Content directory {self.content_dir} does not exist")
|
|
return
|
|
|
|
os.makedirs(self.artifacts_dir, exist_ok=True)
|
|
|
|
for plugin_name in os.listdir(self.content_dir):
|
|
plugin_path = os.path.join(self.content_dir, plugin_name)
|
|
|
|
if not os.path.isdir(plugin_path):
|
|
continue
|
|
|
|
if self.old_mode:
|
|
self.pack_plugin_old_mode(plugin_path, plugin_name)
|
|
else:
|
|
self.pack_plugin_new_mode(plugin_path, plugin_name)
|
|
|
|
def run(self):
|
|
"""Run the plugin packer"""
|
|
args = self.parse_arguments()
|
|
self.old_mode = args.old_mode
|
|
self.pack_plugins()
|
|
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
packer = PluginPacker()
|
|
packer.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |