Convert shell scripts to Python with Joomla/Dolibarr platform support #32

Merged
Copilot merged 8 commits from copilot/convert-scripts-to-python into main 2026-01-04 08:29:37 +00:00
2 changed files with 200 additions and 12 deletions
Showing only changes of commit c471225a93 - Show all commits

View File

@@ -45,7 +45,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
try:
import common
import joomla_manifest
import extension_utils
except ImportError:
copilot-pull-request-reviewer[bot] commented 2026-01-04 08:35:22 +00:00 (Migrated from github.com)
Review

This import will fail because the extension_utils module 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.


This import will fail because the `extension_utils` module 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. ```suggestion ```
print("ERROR: Cannot import required libraries", file=sys.stderr)
sys.exit(1)
@@ -121,7 +121,7 @@ def create_package(
exclude_patterns: Set[str] = None
) -> Path:
"""
Create a distributable ZIP package for a Joomla extension.
Create a distributable ZIP package for a Joomla or Dolibarr extension.
Args:
src_dir: Source directory containing extension files
@@ -137,13 +137,15 @@ def create_package(
if not src_path.is_dir():
common.die(f"Source directory not found: {src_dir}")
# Find and parse manifest
manifest_path = joomla_manifest.find_manifest(src_dir)
manifest_info = joomla_manifest.parse_manifest(manifest_path)
# Detect extension platform and get info
ext_info = extension_utils.get_extension_info(src_dir)
if not ext_info:
common.die(f"No Joomla or Dolibarr extension found in {src_dir}")
# Determine version
if not version:
version = manifest_info.version
version = ext_info.version
# Determine repo name
if not repo_name:
@@ -163,7 +165,8 @@ def create_package(
# Generate ZIP filename
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
# Remove existing ZIP if present
@@ -171,8 +174,9 @@ def create_package(
zip_path.unlink()
common.log_section("Creating Extension Package")
common.log_kv("Extension", manifest_info.name)
common.log_kv("Type", manifest_info.extension_type)
common.log_kv("Platform", ext_info.platform.value.upper())
common.log_kv("Extension", ext_info.name)
common.log_kv("Type", ext_info.extension_type)
common.log_kv("Version", version)
common.log_kv("Source", src_dir)
common.log_kv("Output", str(zip_path))
@@ -207,8 +211,9 @@ def create_package(
# Output JSON for machine consumption
result = {
"status": "ok",
"extension": manifest_info.name,
"ext_type": manifest_info.extension_type,
"platform": ext_info.platform.value,
"extension": ext_info.name,
"ext_type": ext_info.extension_type,
"version": version,
"package": str(zip_path),
copilot-pull-request-reviewer[bot] commented 2026-01-04 08:35:22 +00:00 (Migrated from github.com)
Review

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.

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,
@@ -224,7 +229,7 @@ def create_package(
def main() -> int:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Package Joomla extension as distributable ZIP",
description="Package Joomla or Dolibarr extension as distributable ZIP",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
@@ -239,6 +244,8 @@ Examples:
# Package with custom source
%(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
View 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
github-code-quality[bot] commented 2026-01-04 08:21:24 +00:00 (Migrated from github.com)
Review

Unused import

Import of 'List' is not used.


To fix the problem, remove the unused List type from the typing import while keeping Tuple, which is used in the type annotation for run_validation_script. This keeps the code functionally identical but eliminates the unused import.

Concretely, in scripts/run/validate_all.py at line 36, change from typing import List, Tuple to from typing import Tuple. No other lines need to be modified, and no new methods, imports, or definitions are required.

## 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.
copilot-pull-request-reviewer[bot] commented 2026-01-04 08:35:25 +00:00 (Migrated from github.com)
Review

Import of 'List' is not used.

from typing import Tuple
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",
]
copilot-pull-request-reviewer[bot] commented 2026-01-04 08:35:24 +00:00 (Migrated from github.com)
Review

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.

# 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 = []
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())