Convert shell scripts to Python with Joomla/Dolibarr platform support #32
@@ -45,7 +45,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import common
|
import common
|
||||||
import joomla_manifest
|
import extension_utils
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
|||||||
print("ERROR: Cannot import required libraries", file=sys.stderr)
|
print("ERROR: Cannot import required libraries", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -121,7 +121,7 @@ def create_package(
|
|||||||
exclude_patterns: Set[str] = None
|
exclude_patterns: Set[str] = None
|
||||||
) -> Path:
|
) -> Path:
|
||||||
"""
|
"""
|
||||||
Create a distributable ZIP package for a Joomla extension.
|
Create a distributable ZIP package for a Joomla or Dolibarr extension.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
src_dir: Source directory containing extension files
|
src_dir: Source directory containing extension files
|
||||||
@@ -137,13 +137,15 @@ def create_package(
|
|||||||
if not src_path.is_dir():
|
if not src_path.is_dir():
|
||||||
common.die(f"Source directory not found: {src_dir}")
|
common.die(f"Source directory not found: {src_dir}")
|
||||||
|
|
||||||
# Find and parse manifest
|
# Detect extension platform and get info
|
||||||
manifest_path = joomla_manifest.find_manifest(src_dir)
|
ext_info = extension_utils.get_extension_info(src_dir)
|
||||||
manifest_info = joomla_manifest.parse_manifest(manifest_path)
|
|
||||||
|
if not ext_info:
|
||||||
|
common.die(f"No Joomla or Dolibarr extension found in {src_dir}")
|
||||||
|
|
||||||
# Determine version
|
# Determine version
|
||||||
if not version:
|
if not version:
|
||||||
version = manifest_info.version
|
version = ext_info.version
|
||||||
|
|
||||||
# Determine repo name
|
# Determine repo name
|
||||||
if not repo_name:
|
if not repo_name:
|
||||||
@@ -163,7 +165,8 @@ def create_package(
|
|||||||
|
|
||||||
# Generate ZIP filename
|
# Generate ZIP filename
|
||||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||||
zip_filename = f"{repo_name}-{version}-{manifest_info.extension_type}.zip"
|
platform_suffix = f"{ext_info.platform.value}-{ext_info.extension_type}"
|
||||||
|
zip_filename = f"{repo_name}-{version}-{platform_suffix}.zip"
|
||||||
zip_path = output_path / zip_filename
|
zip_path = output_path / zip_filename
|
||||||
|
|
||||||
# Remove existing ZIP if present
|
# Remove existing ZIP if present
|
||||||
@@ -171,8 +174,9 @@ def create_package(
|
|||||||
zip_path.unlink()
|
zip_path.unlink()
|
||||||
|
|
||||||
common.log_section("Creating Extension Package")
|
common.log_section("Creating Extension Package")
|
||||||
common.log_kv("Extension", manifest_info.name)
|
common.log_kv("Platform", ext_info.platform.value.upper())
|
||||||
common.log_kv("Type", manifest_info.extension_type)
|
common.log_kv("Extension", ext_info.name)
|
||||||
|
common.log_kv("Type", ext_info.extension_type)
|
||||||
common.log_kv("Version", version)
|
common.log_kv("Version", version)
|
||||||
common.log_kv("Source", src_dir)
|
common.log_kv("Source", src_dir)
|
||||||
common.log_kv("Output", str(zip_path))
|
common.log_kv("Output", str(zip_path))
|
||||||
@@ -207,8 +211,9 @@ def create_package(
|
|||||||
# Output JSON for machine consumption
|
# Output JSON for machine consumption
|
||||||
result = {
|
result = {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"extension": manifest_info.name,
|
"platform": ext_info.platform.value,
|
||||||
"ext_type": manifest_info.extension_type,
|
"extension": ext_info.name,
|
||||||
|
"ext_type": ext_info.extension_type,
|
||||||
"version": version,
|
"version": version,
|
||||||
"package": str(zip_path),
|
"package": str(zip_path),
|
||||||
|
The reference to The reference to `ext_info.platform` will fail because the `extension_utils` module that defines the `ExtensionInfo` class and `platform` attribute is missing from this PR.
|
|||||||
"files": file_count,
|
"files": file_count,
|
||||||
@@ -224,7 +229,7 @@ def create_package(
|
|||||||
def main() -> int:
|
def main() -> int:
|
||||||
"""Main entry point."""
|
"""Main entry point."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Package Joomla extension as distributable ZIP",
|
description="Package Joomla or Dolibarr extension as distributable ZIP",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog="""
|
epilog="""
|
||||||
Examples:
|
Examples:
|
||||||
@@ -239,6 +244,8 @@ Examples:
|
|||||||
|
|
||||||
# Package with custom source
|
# Package with custom source
|
||||||
%(prog)s --src-dir my-extension dist 1.0.0
|
%(prog)s --src-dir my-extension dist 1.0.0
|
||||||
|
|
||||||
|
Supports both Joomla and Dolibarr extensions with automatic platform detection.
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
181
scripts/run/validate_all.py
Executable file
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Run all validation scripts.
|
||||||
|
|
||||||
|
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
|
||||||
|
This file is part of a Moko Consulting project.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program (./LICENSE.md).
|
||||||
|
|
||||||
|
FILE INFORMATION
|
||||||
|
DEFGROUP: Script.Run
|
||||||
|
INGROUP: Validation.Runner
|
||||||
|
REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia
|
||||||
|
PATH: /scripts/run/validate_all.py
|
||||||
|
VERSION: 01.00.00
|
||||||
|
BRIEF: Run all validation scripts
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Tuple
|
||||||
Unused importImport of 'List' is not used. To fix the problem, remove the unused Concretely, in ## Unused import
Import of 'List' is not used.
---
To fix the problem, remove the unused <code>List</code> type from the <code>typing</code> import while keeping <code>Tuple</code>, which is used in the type annotation for <code>run_validation_script</code>. This keeps the code functionally identical but eliminates the unused import.</p>
<p>Concretely, in <code>scripts/run/validate_all.py</code> at line 36, change <code>from typing import List, Tuple</code> to <code>from typing import Tuple</code>. No other lines need to be modified, and no new methods, imports, or definitions are required.
Import of 'List' is not used. Import of 'List' is not used.
```suggestion
from typing import Tuple
```
|
|||||||
|
|
||||||
|
# Add lib directory to path
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
import common
|
||||||
|
except ImportError:
|
||||||
|
print("ERROR: Cannot import required libraries", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Required validation scripts (must pass)
|
||||||
|
REQUIRED_SCRIPTS = [
|
||||||
|
"scripts/validate/manifest.py",
|
||||||
|
"scripts/validate/xml_wellformed.py",
|
||||||
|
"scripts/validate/workflows.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Optional validation scripts (failures are warnings)
|
||||||
|
OPTIONAL_SCRIPTS = [
|
||||||
|
"scripts/validate/changelog.py",
|
||||||
|
"scripts/validate/language_structure.py",
|
||||||
|
"scripts/validate/license_headers.py",
|
||||||
|
"scripts/validate/no_secrets.py",
|
||||||
|
"scripts/validate/paths.py",
|
||||||
|
"scripts/validate/php_syntax.py",
|
||||||
|
"scripts/validate/tabs.py",
|
||||||
|
"scripts/validate/version_alignment.py",
|
||||||
|
"scripts/validate/version_hierarchy.py",
|
||||||
|
]
|
||||||
|
The OPTIONAL_SCRIPTS list references Python files that don't exist in this PR. When these files are not found, they will be skipped, but the code still references them as if they should exist. Consider removing non-existent scripts from this list or adding a comment indicating they haven't been converted yet. The OPTIONAL_SCRIPTS list references Python files that don't exist in this PR. When these files are not found, they will be skipped, but the code still references them as if they should exist. Consider removing non-existent scripts from this list or adding a comment indicating they haven't been converted yet.
```suggestion
# NOTE:
# The following optional validators are planned but their Python implementations
# may not yet exist in this repository/PR:
# - scripts/validate/changelog.py
# - scripts/validate/language_structure.py
# - scripts/validate/license_headers.py
# - scripts/validate/no_secrets.py
# - scripts/validate/paths.py
# - scripts/validate/php_syntax.py
# - scripts/validate/tabs.py
# - scripts/validate/version_alignment.py
# - scripts/validate/version_hierarchy.py
# They are intentionally not included in OPTIONAL_SCRIPTS until implemented.
OPTIONAL_SCRIPTS = []
```
|
|||||||
|
|
||||||
|
|
||||||
|
def run_validation_script(script_path: str) -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Run a validation script.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
script_path: Path to script
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (success, output)
|
||||||
|
"""
|
||||||
|
script = Path(script_path)
|
||||||
|
|
||||||
|
if not script.exists():
|
||||||
|
return (False, f"Script not found: {script_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["python3", str(script)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False
|
||||||
|
)
|
||||||
|
|
||||||
|
output = result.stdout + result.stderr
|
||||||
|
success = result.returncode == 0
|
||||||
|
|
||||||
|
return (success, output)
|
||||||
|
except Exception as e:
|
||||||
|
return (False, f"Error running script: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""Main entry point."""
|
||||||
|
common.log_section("Running All Validations")
|
||||||
|
print()
|
||||||
|
|
||||||
|
total_passed = 0
|
||||||
|
total_failed = 0
|
||||||
|
total_skipped = 0
|
||||||
|
|
||||||
|
# Run required scripts
|
||||||
|
common.log_info("=== Required Validations ===")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for script in REQUIRED_SCRIPTS:
|
||||||
|
script_name = Path(script).name
|
||||||
|
common.log_info(f"Running {script_name}...")
|
||||||
|
|
||||||
|
success, output = run_validation_script(script)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
common.log_success(f"✓ {script_name} passed")
|
||||||
|
total_passed += 1
|
||||||
|
else:
|
||||||
|
common.log_error(f"✗ {script_name} FAILED")
|
||||||
|
if output:
|
||||||
|
print(output)
|
||||||
|
total_failed += 1
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Run optional scripts
|
||||||
|
common.log_info("=== Optional Validations ===")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for script in OPTIONAL_SCRIPTS:
|
||||||
|
script_name = Path(script).name
|
||||||
|
|
||||||
|
if not Path(script).exists():
|
||||||
|
common.log_warn(f"⊘ {script_name} not found (skipped)")
|
||||||
|
total_skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
common.log_info(f"Running {script_name}...")
|
||||||
|
|
||||||
|
success, output = run_validation_script(script)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
common.log_success(f"✓ {script_name} passed")
|
||||||
|
total_passed += 1
|
||||||
|
else:
|
||||||
|
common.log_warn(f"⚠ {script_name} failed (optional)")
|
||||||
|
if output:
|
||||||
|
print(output[:500]) # Limit output
|
||||||
|
total_failed += 1
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
common.log_section("Validation Summary")
|
||||||
|
common.log_kv("Total Passed", str(total_passed))
|
||||||
|
common.log_kv("Total Failed", str(total_failed))
|
||||||
|
common.log_kv("Total Skipped", str(total_skipped))
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Check if any required validations failed
|
||||||
|
required_failed = sum(
|
||||||
|
1 for script in REQUIRED_SCRIPTS
|
||||||
|
if Path(script).exists() and not run_validation_script(script)[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
if required_failed > 0:
|
||||||
|
common.log_error(f"{required_failed} required validation(s) failed")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
common.log_success("All required validations passed!")
|
||||||
|
|
||||||
|
if total_failed > 0:
|
||||||
|
common.log_warn(f"{total_failed} optional validation(s) failed")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
This import will fail because the
extension_utilsmodule is not included in the pull request. The PR description mentions adding this file, but it's missing from the diffs. This will cause the script to exit immediately with an ImportError.