Add dev tools and make build/release platform-aware (Joomla/Dolibarr)
Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com>
This commit is contained in:
63
.github/workflows/release_pipeline.yml
vendored
63
.github/workflows/release_pipeline.yml
vendored
@@ -643,7 +643,7 @@ else:
|
|||||||
echo "```"
|
echo "```"
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
- name: Build Joomla ZIP (extension type aware, src-only archive)
|
- name: Build Joomla/Dolibarr ZIP (platform-aware, src-only archive)
|
||||||
id: build
|
id: build
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -657,44 +657,49 @@ else:
|
|||||||
DIST_DIR="${GITHUB_WORKSPACE}/dist"
|
DIST_DIR="${GITHUB_WORKSPACE}/dist"
|
||||||
mkdir -p "${DIST_DIR}"
|
mkdir -p "${DIST_DIR}"
|
||||||
|
|
||||||
MANIFEST=""
|
# Detect platform and extension type using Python utility
|
||||||
if [ -f "src/templateDetails.xml" ]; then
|
PLATFORM_INFO=$(python3 -c "
|
||||||
MANIFEST="src/templateDetails.xml"
|
import sys
|
||||||
elif find src -maxdepth 4 -type f -name 'templateDetails.xml' | head -n 1 | grep -q .; then
|
sys.path.insert(0, '${GITHUB_WORKSPACE}/scripts/lib')
|
||||||
MANIFEST="$(find src -maxdepth 4 -type f -name 'templateDetails.xml' | head -n 1)"
|
import extension_utils
|
||||||
elif find src -maxdepth 4 -type f -name 'pkg_*.xml' | head -n 1 | grep -q .; then
|
ext_info = extension_utils.get_extension_info('${GITHUB_WORKSPACE}/src')
|
||||||
MANIFEST="$(find src -maxdepth 4 -type f -name 'pkg_*.xml' | head -n 1)"
|
if ext_info:
|
||||||
elif find src -maxdepth 4 -type f -name 'com_*.xml' | head -n 1 | grep -q .; then
|
print(f'{ext_info.platform.value}|{ext_info.extension_type}')
|
||||||
MANIFEST="$(find src -maxdepth 4 -type f -name 'com_*.xml' | head -n 1)"
|
else:
|
||||||
elif find src -maxdepth 4 -type f -name 'mod_*.xml' | head -n 1 | grep -q .; then
|
sys.exit(1)
|
||||||
MANIFEST="$(find src -maxdepth 4 -type f -name 'mod_*.xml' | head -n 1)"
|
")
|
||||||
elif find src -maxdepth 6 -type f -name 'plg_*.xml' | head -n 1 | grep -q .; then
|
|
||||||
MANIFEST="$(find src -maxdepth 6 -type f -name 'plg_*.xml' | head -n 1)"
|
if [ $? -ne 0 ]; then
|
||||||
else
|
echo "ERROR: Could not detect extension platform and type" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
MANIFEST="$(grep -Rsl --include='*.xml' '<extension' src | head -n 1 || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${MANIFEST}" ]; then
|
|
||||||
echo "ERROR: No Joomla manifest XML found under src" >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
PLATFORM="${PLATFORM_INFO%%|*}"
|
||||||
|
EXT_TYPE="${PLATFORM_INFO##*|}"
|
||||||
|
|
||||||
EXT_TYPE="$(grep -Eo 'type="[^"]+"' "${MANIFEST}" | head -n 1 | cut -d '"' -f2 || true)"
|
ZIP="${REPO_NAME}-${VERSION}-${CHANNEL}-${PLATFORM}-${EXT_TYPE}.zip"
|
||||||
if [ -z "${EXT_TYPE}" ]; then
|
|
||||||
EXT_TYPE="unknown"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ZIP="${REPO_NAME}-${VERSION}-${CHANNEL}-${EXT_TYPE}.zip"
|
|
||||||
|
|
||||||
|
# Create ZIP with development artifact exclusions
|
||||||
zip -r -X "${DIST_DIR}/${ZIP}" src \
|
zip -r -X "${DIST_DIR}/${ZIP}" src \
|
||||||
-x "src/**/.git/**" \
|
-x "src/**/.git/**" \
|
||||||
-x "src/**/.github/**" \
|
-x "src/**/.github/**" \
|
||||||
-x "src/**/.DS_Store" \
|
-x "src/**/.DS_Store" \
|
||||||
-x "src/**/__MACOSX/**"
|
-x "src/**/__MACOSX/**" \
|
||||||
|
-x "src/**/node_modules/**" \
|
||||||
|
-x "src/**/vendor/**" \
|
||||||
|
-x "src/**/tests/**" \
|
||||||
|
-x "src/**/Tests/**" \
|
||||||
|
-x "src/**/.phpstan.cache/**" \
|
||||||
|
-x "src/**/.psalm/**" \
|
||||||
|
-x "src/**/.rector/**" \
|
||||||
|
-x "src/**/phpmd-cache/**" \
|
||||||
|
-x "src/**/.php-cs-fixer.cache" \
|
||||||
|
-x "src/**/.phplint-cache" \
|
||||||
|
-x "src/**/*.log"
|
||||||
|
|
||||||
echo "zip_name=${ZIP}" >> "${GITHUB_OUTPUT}"
|
echo "zip_name=${ZIP}" >> "${GITHUB_OUTPUT}"
|
||||||
echo "dist_dir=${DIST_DIR}" >> "${GITHUB_OUTPUT}"
|
echo "dist_dir=${DIST_DIR}" >> "${GITHUB_OUTPUT}"
|
||||||
echo "manifest=${MANIFEST}" >> "${GITHUB_OUTPUT}"
|
echo "platform=${PLATFORM}" >> "${GITHUB_OUTPUT}"
|
||||||
echo "ext_type=${EXT_TYPE}" >> "${GITHUB_OUTPUT}"
|
echo "ext_type=${EXT_TYPE}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
ZIP_BYTES="$(stat -c%s "${DIST_DIR}/${ZIP}")"
|
ZIP_BYTES="$(stat -c%s "${DIST_DIR}/${ZIP}")"
|
||||||
@@ -702,7 +707,7 @@ else:
|
|||||||
{
|
{
|
||||||
echo "### Build report"
|
echo "### Build report"
|
||||||
echo "```json"
|
echo "```json"
|
||||||
echo "{\"repository\":\"${GITHUB_REPOSITORY}\",\"workflow\":\"${GITHUB_WORKFLOW}\",\"job\":\"${GITHUB_JOB}\",\"run/id\":${GITHUB_RUN_ID},\"run/number\":${GITHUB_RUN_NUMBER},\"run/attempt\":${GITHUB_RUN_ATTEMPT},\"run/url\":\"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}\",\"actor\":\"${GITHUB_ACTOR}\",\"sha\":\"${GITHUB_SHA}\",\"archive_policy\":\"src_only\",\"manifest\":\"${MANIFEST}\",\"extension_type\":\"${EXT_TYPE}\",\"zip\":\"${DIST_DIR}/${ZIP}\",\"zip_bytes\":${ZIP_BYTES}}"
|
echo "{\"repository\":\"${GITHUB_REPOSITORY}\",\"workflow\":\"${GITHUB_WORKFLOW}\",\"job\":\"${GITHUB_JOB}\",\"run/id\":${GITHUB_RUN_ID},\"run/number\":${GITHUB_RUN_NUMBER},\"run/attempt\":${GITHUB_RUN_ATTEMPT},\"run/url\":\"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}\",\"actor\":\"${GITHUB_ACTOR}\",\"sha\":\"${GITHUB_SHA}\",\"archive_policy\":\"src_only\",\"platform\":\"${PLATFORM}\",\"extension_type\":\"${EXT_TYPE}\",\"zip\":\"${DIST_DIR}/${ZIP}\",\"zip_bytes\":${ZIP_BYTES}}"
|
||||||
echo "```"
|
echo "```"
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
|
|||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -793,6 +793,14 @@ package-lock.json
|
|||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
codeception.phar
|
codeception.phar
|
||||||
|
|
||||||
|
# Development tool artifacts
|
||||||
|
.phpstan.cache
|
||||||
|
.psalm/
|
||||||
|
.rector/
|
||||||
|
phpmd-cache/
|
||||||
|
.php-cs-fixer.cache
|
||||||
|
.phplint-cache
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
@@ -804,8 +812,8 @@ develop-eggs/
|
|||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
/lib/
|
||||||
lib64/
|
/lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
|||||||
42
Makefile
42
Makefile
@@ -26,8 +26,12 @@ install:
|
|||||||
@command -v composer >/dev/null 2>&1 || { echo "Error: composer not found. Please install composer first."; exit 1; }
|
@command -v composer >/dev/null 2>&1 || { echo "Error: composer not found. Please install composer first."; exit 1; }
|
||||||
composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
|
composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
|
||||||
composer global require "phpstan/phpstan:^1.0" --with-all-dependencies
|
composer global require "phpstan/phpstan:^1.0" --with-all-dependencies
|
||||||
|
composer global require "phpstan/extension-installer:^1.0" --with-all-dependencies
|
||||||
composer global require "phpcompatibility/php-compatibility:^9.0" --with-all-dependencies
|
composer global require "phpcompatibility/php-compatibility:^9.0" --with-all-dependencies
|
||||||
composer global require "codeception/codeception" --with-all-dependencies
|
composer global require "codeception/codeception" --with-all-dependencies
|
||||||
|
composer global require "vimeo/psalm:^5.0" --with-all-dependencies
|
||||||
|
composer global require "phpmd/phpmd:^2.0" --with-all-dependencies
|
||||||
|
composer global require "friendsofphp/php-cs-fixer:^3.0" --with-all-dependencies
|
||||||
phpcs --config-set installed_paths ~/.composer/vendor/phpcompatibility/php-compatibility
|
phpcs --config-set installed_paths ~/.composer/vendor/phpcompatibility/php-compatibility
|
||||||
@echo "✓ Dependencies installed"
|
@echo "✓ Dependencies installed"
|
||||||
|
|
||||||
@@ -93,6 +97,38 @@ phpcompat:
|
|||||||
@command -v phpcs >/dev/null 2>&1 || { echo "Error: phpcs not found. Run 'make install' first."; exit 1; }
|
@command -v phpcs >/dev/null 2>&1 || { echo "Error: phpcs not found. Run 'make install' first."; exit 1; }
|
||||||
phpcs --standard=PHPCompatibility --runtime-set testVersion 8.0- src/
|
phpcs --standard=PHPCompatibility --runtime-set testVersion 8.0- src/
|
||||||
|
|
||||||
|
## psalm: Run Psalm static analysis
|
||||||
|
psalm:
|
||||||
|
@echo "Running Psalm static analysis..."
|
||||||
|
@command -v psalm >/dev/null 2>&1 || { echo "Error: psalm not found. Run 'make install' first."; exit 1; }
|
||||||
|
psalm --show-info=false
|
||||||
|
|
||||||
|
## phpmd: Run PHP Mess Detector
|
||||||
|
phpmd:
|
||||||
|
@echo "Running PHP Mess Detector..."
|
||||||
|
@command -v phpmd >/dev/null 2>&1 || { echo "Error: phpmd not found. Run 'make install' first."; exit 1; }
|
||||||
|
phpmd src/ text cleancode,codesize,controversial,design,naming,unusedcode
|
||||||
|
|
||||||
|
## php-cs-fixer: Run PHP-CS-Fixer
|
||||||
|
php-cs-fixer:
|
||||||
|
@echo "Running PHP-CS-Fixer..."
|
||||||
|
@command -v php-cs-fixer >/dev/null 2>&1 || { echo "Error: php-cs-fixer not found. Run 'make install' first."; exit 1; }
|
||||||
|
php-cs-fixer fix --dry-run --diff src/
|
||||||
|
|
||||||
|
## php-cs-fixer-fix: Auto-fix with PHP-CS-Fixer
|
||||||
|
php-cs-fixer-fix:
|
||||||
|
@echo "Auto-fixing with PHP-CS-Fixer..."
|
||||||
|
@command -v php-cs-fixer >/dev/null 2>&1 || { echo "Error: php-cs-fixer not found. Run 'make install' first."; exit 1; }
|
||||||
|
php-cs-fixer fix src/
|
||||||
|
|
||||||
|
## quality-extended: Run extended quality checks (includes psalm, phpmd)
|
||||||
|
quality-extended:
|
||||||
|
@echo "Running extended code quality checks..."
|
||||||
|
@$(MAKE) quality
|
||||||
|
@$(MAKE) psalm
|
||||||
|
@$(MAKE) phpmd
|
||||||
|
@echo "✓ All quality checks passed"
|
||||||
|
|
||||||
## package: Create distribution package
|
## package: Create distribution package
|
||||||
package:
|
package:
|
||||||
@echo "Creating distribution package..."
|
@echo "Creating distribution package..."
|
||||||
@@ -128,6 +164,12 @@ clean:
|
|||||||
@rm -rf dist/
|
@rm -rf dist/
|
||||||
@rm -rf tests/_output/
|
@rm -rf tests/_output/
|
||||||
@rm -rf .phpunit.cache/
|
@rm -rf .phpunit.cache/
|
||||||
|
@rm -rf .phpstan.cache/
|
||||||
|
@rm -rf .psalm/
|
||||||
|
@rm -rf .rector/
|
||||||
|
@rm -rf phpmd-cache/
|
||||||
|
@find . -type f -name ".php-cs-fixer.cache" -delete
|
||||||
|
@find . -type f -name ".phplint-cache" -delete
|
||||||
@find . -type f -name "*.log" -delete
|
@find . -type f -name "*.log" -delete
|
||||||
@find . -type f -name ".DS_Store" -delete
|
@find . -type f -name ".DS_Store" -delete
|
||||||
@echo "✓ Cleaned"
|
@echo "✓ Cleaned"
|
||||||
|
|||||||
349
scripts/lib/extension_utils.py
Normal file
349
scripts/lib/extension_utils.py
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Extension utilities for Joomla and Dolibarr.
|
||||||
|
|
||||||
|
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.Library
|
||||||
|
INGROUP: Extension.Utils
|
||||||
|
REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia
|
||||||
|
PATH: /scripts/lib/extension_utils.py
|
||||||
|
VERSION: 01.00.00
|
||||||
|
BRIEF: Platform-aware extension utilities for Joomla and Dolibarr
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
class Platform(Enum):
|
||||||
|
"""Supported extension platforms."""
|
||||||
|
JOOMLA = "joomla"
|
||||||
|
DOLIBARR = "dolibarr"
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExtensionInfo:
|
||||||
|
"""Extension information."""
|
||||||
|
platform: Platform
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
extension_type: str
|
||||||
|
manifest_path: Path
|
||||||
|
description: Optional[str] = None
|
||||||
|
author: Optional[str] = None
|
||||||
|
author_email: Optional[str] = None
|
||||||
|
license: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
def detect_joomla_manifest(src_dir: Union[str, Path]) -> Optional[Path]:
|
||||||
|
"""
|
||||||
|
Detect Joomla manifest file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src_dir: Source directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to manifest file or None
|
||||||
|
"""
|
||||||
|
src_path = Path(src_dir)
|
||||||
|
|
||||||
|
# Common Joomla manifest locations and patterns
|
||||||
|
manifest_patterns = [
|
||||||
|
"templateDetails.xml",
|
||||||
|
"pkg_*.xml",
|
||||||
|
"com_*.xml",
|
||||||
|
"mod_*.xml",
|
||||||
|
"plg_*.xml",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Search in src_dir and subdirectories (max depth 4)
|
||||||
|
for pattern in manifest_patterns:
|
||||||
|
# Direct match
|
||||||
|
matches = list(src_path.glob(pattern))
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
# Search in subdirectories
|
||||||
|
matches = list(src_path.glob(f"*/{pattern}"))
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
matches = list(src_path.glob(f"*/*/{pattern}"))
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
# Fallback: search for any XML with <extension tag
|
||||||
|
for xml_file in src_path.rglob("*.xml"):
|
||||||
|
if xml_file.name.startswith("."):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
tree = ET.parse(xml_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
if root.tag == "extension":
|
||||||
|
return xml_file
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def detect_dolibarr_manifest(src_dir: Union[str, Path]) -> Optional[Path]:
|
||||||
|
"""
|
||||||
|
Detect Dolibarr module descriptor file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src_dir: Source directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to descriptor file or None
|
||||||
|
"""
|
||||||
|
src_path = Path(src_dir)
|
||||||
|
|
||||||
|
# Dolibarr module descriptors follow pattern: core/modules/modMyModule.class.php
|
||||||
|
descriptor_patterns = [
|
||||||
|
"core/modules/mod*.class.php",
|
||||||
|
"*/modules/mod*.class.php",
|
||||||
|
"mod*.class.php",
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in descriptor_patterns:
|
||||||
|
matches = list(src_path.glob(pattern))
|
||||||
|
if matches:
|
||||||
|
# Verify it's actually a Dolibarr module descriptor
|
||||||
|
for match in matches:
|
||||||
|
try:
|
||||||
|
content = match.read_text(encoding="utf-8")
|
||||||
|
if "extends DolibarrModules" in content or "class Mod" in content:
|
||||||
|
return match
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_joomla_manifest(manifest_path: Path) -> Optional[ExtensionInfo]:
|
||||||
|
"""
|
||||||
|
Parse Joomla manifest XML.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
manifest_path: Path to manifest file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExtensionInfo or None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tree = ET.parse(manifest_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
if root.tag != "extension":
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get extension type
|
||||||
|
ext_type = root.get("type", "unknown")
|
||||||
|
|
||||||
|
# Get name
|
||||||
|
name_elem = root.find("name")
|
||||||
|
name = name_elem.text if name_elem is not None else "unknown"
|
||||||
|
|
||||||
|
# Get version
|
||||||
|
version_elem = root.find("version")
|
||||||
|
version = version_elem.text if version_elem is not None else "0.0.0"
|
||||||
|
|
||||||
|
# Get description
|
||||||
|
desc_elem = root.find("description")
|
||||||
|
description = desc_elem.text if desc_elem is not None else None
|
||||||
|
|
||||||
|
# Get author
|
||||||
|
author_elem = root.find("author")
|
||||||
|
author = author_elem.text if author_elem is not None else None
|
||||||
|
|
||||||
|
# Get author email
|
||||||
|
author_email_elem = root.find("authorEmail")
|
||||||
|
author_email = author_email_elem.text if author_email_elem is not None else None
|
||||||
|
|
||||||
|
# Get license
|
||||||
|
license_elem = root.find("license")
|
||||||
|
license_text = license_elem.text if license_elem is not None else None
|
||||||
|
|
||||||
|
return ExtensionInfo(
|
||||||
|
platform=Platform.JOOMLA,
|
||||||
|
name=name,
|
||||||
|
version=version,
|
||||||
|
extension_type=ext_type,
|
||||||
|
manifest_path=manifest_path,
|
||||||
|
description=description,
|
||||||
|
author=author,
|
||||||
|
author_email=author_email,
|
||||||
|
license=license_text
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dolibarr_descriptor(descriptor_path: Path) -> Optional[ExtensionInfo]:
|
||||||
|
"""
|
||||||
|
Parse Dolibarr module descriptor PHP file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
descriptor_path: Path to descriptor file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExtensionInfo or None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
content = descriptor_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# Extract module name from class name
|
||||||
|
name_match = re.search(r'class\s+(Mod\w+)', content)
|
||||||
|
name = name_match.group(1) if name_match else "unknown"
|
||||||
|
|
||||||
|
# Extract version
|
||||||
|
version_match = re.search(r'\$this->version\s*=\s*[\'"]([^\'"]+)[\'"]', content)
|
||||||
|
version = version_match.group(1) if version_match else "0.0.0"
|
||||||
|
|
||||||
|
# Extract description
|
||||||
|
desc_match = re.search(r'\$this->description\s*=\s*[\'"]([^\'"]+)[\'"]', content)
|
||||||
|
description = desc_match.group(1) if desc_match else None
|
||||||
|
|
||||||
|
# Extract author
|
||||||
|
author_match = re.search(r'\$this->editor_name\s*=\s*[\'"]([^\'"]+)[\'"]', content)
|
||||||
|
author = author_match.group(1) if author_match else None
|
||||||
|
|
||||||
|
return ExtensionInfo(
|
||||||
|
platform=Platform.DOLIBARR,
|
||||||
|
name=name,
|
||||||
|
version=version,
|
||||||
|
extension_type="module",
|
||||||
|
manifest_path=descriptor_path,
|
||||||
|
description=description,
|
||||||
|
author=author,
|
||||||
|
author_email=None,
|
||||||
|
license=None
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_extension_info(src_dir: Union[str, Path]) -> Optional[ExtensionInfo]:
|
||||||
|
"""
|
||||||
|
Detect and parse extension information from source directory.
|
||||||
|
Supports both Joomla and Dolibarr platforms.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src_dir: Source directory containing extension files
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExtensionInfo or None if not detected
|
||||||
|
"""
|
||||||
|
src_path = Path(src_dir)
|
||||||
|
|
||||||
|
if not src_path.is_dir():
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Try Joomla first
|
||||||
|
joomla_manifest = detect_joomla_manifest(src_path)
|
||||||
|
if joomla_manifest:
|
||||||
|
ext_info = parse_joomla_manifest(joomla_manifest)
|
||||||
|
if ext_info:
|
||||||
|
return ext_info
|
||||||
|
|
||||||
|
# Try Dolibarr
|
||||||
|
dolibarr_descriptor = detect_dolibarr_manifest(src_path)
|
||||||
|
if dolibarr_descriptor:
|
||||||
|
ext_info = parse_dolibarr_descriptor(dolibarr_descriptor)
|
||||||
|
if ext_info:
|
||||||
|
return ext_info
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_joomla_extension(src_dir: Union[str, Path]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if directory contains a Joomla extension.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src_dir: Source directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if Joomla extension detected
|
||||||
|
"""
|
||||||
|
ext_info = get_extension_info(src_dir)
|
||||||
|
return ext_info is not None and ext_info.platform == Platform.JOOMLA
|
||||||
|
|
||||||
|
|
||||||
|
def is_dolibarr_extension(src_dir: Union[str, Path]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if directory contains a Dolibarr module.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src_dir: Source directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if Dolibarr module detected
|
||||||
|
"""
|
||||||
|
ext_info = get_extension_info(src_dir)
|
||||||
|
return ext_info is not None and ext_info.platform == Platform.DOLIBARR
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Test the extension utilities."""
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
import common
|
||||||
|
|
||||||
|
common.log_section("Testing Extension Utilities")
|
||||||
|
|
||||||
|
# Test with current directory's src
|
||||||
|
repo_root = common.repo_root()
|
||||||
|
src_dir = repo_root / "src"
|
||||||
|
|
||||||
|
if not src_dir.is_dir():
|
||||||
|
common.log_warn(f"Source directory not found: {src_dir}")
|
||||||
|
return
|
||||||
|
|
||||||
|
ext_info = get_extension_info(src_dir)
|
||||||
|
|
||||||
|
if ext_info:
|
||||||
|
common.log_success("Extension detected!")
|
||||||
|
common.log_kv("Platform", ext_info.platform.value.upper())
|
||||||
|
common.log_kv("Name", ext_info.name)
|
||||||
|
common.log_kv("Version", ext_info.version)
|
||||||
|
common.log_kv("Type", ext_info.extension_type)
|
||||||
|
common.log_kv("Manifest", str(ext_info.manifest_path))
|
||||||
|
if ext_info.description:
|
||||||
|
common.log_kv("Description", ext_info.description[:60] + "...")
|
||||||
|
if ext_info.author:
|
||||||
|
common.log_kv("Author", ext_info.author)
|
||||||
|
else:
|
||||||
|
common.log_error("No extension detected")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -62,6 +62,9 @@ EXCLUDE_PATTERNS = {
|
|||||||
# Documentation (optional, can be included)
|
# Documentation (optional, can be included)
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
"dist", "build", ".phpunit.cache",
|
"dist", "build", ".phpunit.cache",
|
||||||
|
# Development tool caches and artifacts
|
||||||
|
".phpstan.cache", ".psalm", ".rector",
|
||||||
|
"phpmd-cache", ".php-cs-fixer.cache", ".phplint-cache",
|
||||||
# OS files
|
# OS files
|
||||||
".DS_Store", "Thumbs.db",
|
".DS_Store", "Thumbs.db",
|
||||||
# Logs
|
# Logs
|
||||||
@@ -78,10 +81,11 @@ EXCLUDE_PATTERNS = {
|
|||||||
"composer.json", "composer.lock",
|
"composer.json", "composer.lock",
|
||||||
"package.json", "package-lock.json",
|
"package.json", "package-lock.json",
|
||||||
"phpunit.xml", "phpstan.neon", "phpcs.xml",
|
"phpunit.xml", "phpstan.neon", "phpcs.xml",
|
||||||
"codeception.yml",
|
"codeception.yml", "psalm.xml", ".php-cs-fixer.php",
|
||||||
# Others
|
# Others
|
||||||
"README.md", "CHANGELOG.md", "CONTRIBUTING.md",
|
"README.md", "CHANGELOG.md", "CONTRIBUTING.md",
|
||||||
"CODE_OF_CONDUCT.md", "SECURITY.md", "GOVERNANCE.md",
|
"CODE_OF_CONDUCT.md", "SECURITY.md", "GOVERNANCE.md",
|
||||||
|
"Makefile",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user