From 638f2fb5ad6a319715f2c5a621fe815c1fb88ec8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:12:03 +0000 Subject: [PATCH 1/8] Initial plan -- 2.49.1 From 4ad79f27d2b9cf6acff1ea095668ee750a8c9a7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:16:35 +0000 Subject: [PATCH 2/8] Add Dolibarr support and convert tabs/no_secrets validation to Python Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- scripts/validate/no_secrets.py | 212 +++++++++++++++++++++++++++++++++ scripts/validate/tabs.py | 140 ++++++++++++++++++++++ 2 files changed, 352 insertions(+) create mode 100644 scripts/validate/no_secrets.py create mode 100644 scripts/validate/tabs.py diff --git a/scripts/validate/no_secrets.py b/scripts/validate/no_secrets.py new file mode 100644 index 0000000..39e0f29 --- /dev/null +++ b/scripts/validate/no_secrets.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +""" +Scan for accidentally committed secrets and credentials. + +Copyright (C) 2025 Moko Consulting + +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.Validate +INGROUP: Security +REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia +PATH: /scripts/validate/no_secrets.py +VERSION: 01.00.00 +BRIEF: Scan for accidentally committed secrets and credentials +NOTE: High-signal pattern detection to prevent credential exposure +""" + +import argparse +import json +import os +import re +import sys +from pathlib import Path +from typing import List, Dict + +# 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) + + +# High-signal patterns only. Any match is a hard fail. +SECRET_PATTERNS = [ + # Private keys + r'-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----', + r'PuTTY-User-Key-File-', + # AWS keys + r'AKIA[0-9A-Z]{16}', + r'ASIA[0-9A-Z]{16}', + # GitHub tokens + r'ghp_[A-Za-z0-9]{36}', + r'gho_[A-Za-z0-9]{36}', + r'github_pat_[A-Za-z0-9_]{20,}', + # Slack tokens + r'xox[baprs]-[0-9A-Za-z-]{10,48}', + # Stripe keys + r'sk_live_[0-9a-zA-Z]{20,}', +] + +# Directories to exclude from scanning +EXCLUDE_DIRS = { + 'vendor', + 'node_modules', + 'dist', + 'build', + '.git', +} + + +def scan_file(filepath: Path, patterns: List[re.Pattern]) -> List[Dict[str, str]]: + """ + Scan a file for secret patterns. + + Args: + filepath: Path to file to scan + patterns: Compiled regex patterns to search for + + Returns: + List of matches with file, line number, and content + """ + hits = [] + + try: + with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: + for line_num, line in enumerate(f, 1): + for pattern in patterns: + if pattern.search(line): + hits.append({ + 'file': str(filepath), + 'line': line_num, + 'content': line.strip()[:100] # Limit to 100 chars + }) + except Exception as e: + common.log_warn(f"Could not read {filepath}: {e}") + + return hits + + +def scan_directory(src_dir: str, patterns: List[re.Pattern]) -> List[Dict[str, str]]: + """ + Recursively scan directory for secrets. + + Args: + src_dir: Directory to scan + patterns: Compiled regex patterns + + Returns: + List of all matches + """ + src_path = Path(src_dir) + all_hits = [] + + for item in src_path.rglob("*"): + # Skip directories + if not item.is_file(): + continue + + # Skip excluded directories + if any(excluded in item.parts for excluded in EXCLUDE_DIRS): + continue + + # Skip binary files (heuristic) + try: + with open(item, 'rb') as f: + chunk = f.read(1024) + if b'\x00' in chunk: # Contains null bytes = likely binary + continue + except Exception: + continue + + # Scan the file + hits = scan_file(item, patterns) + all_hits.extend(hits) + + return all_hits + + +def main() -> int: + """Main entry point.""" + parser = argparse.ArgumentParser( + description="Scan for accidentally committed secrets and credentials" + ) + parser.add_argument( + "-s", "--src-dir", + default=os.environ.get("SRC_DIR", "src"), + help="Source directory to scan (default: src)" + ) + + args = parser.parse_args() + + # Check if source directory exists + if not Path(args.src_dir).is_dir(): + result = { + "status": "fail", + "error": "src directory missing" + } + common.json_output(result) + return 1 + + # Compile patterns + compiled_patterns = [re.compile(pattern) for pattern in SECRET_PATTERNS] + + # Scan directory + hits = scan_directory(args.src_dir, compiled_patterns) + + if hits: + # Limit to first 50 hits + hits = hits[:50] + + result = { + "status": "fail", + "error": "secret_pattern_detected", + "hits": [{"hit": f"{h['file']}:{h['line']}: {h['content']}"} for h in hits] + } + + print(json.dumps(result)) + + # Also print human-readable output + print("\nERROR: Potential secrets detected!", file=sys.stderr) + print(f"\nFound {len(hits)} potential secret(s):", file=sys.stderr) + for hit in hits[:10]: # Show first 10 in detail + print(f" {hit['file']}:{hit['line']}", file=sys.stderr) + print(f" {hit['content']}", file=sys.stderr) + + if len(hits) > 10: + print(f" ... and {len(hits) - 10} more", file=sys.stderr) + + print("\nPlease remove any secrets and use environment variables or secret management instead.", file=sys.stderr) + + return 1 + + result = { + "status": "ok", + "src_dir": args.src_dir + } + common.json_output(result) + print("no_secrets: ok") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/validate/tabs.py b/scripts/validate/tabs.py new file mode 100644 index 0000000..fefff29 --- /dev/null +++ b/scripts/validate/tabs.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +""" +Detect TAB characters in YAML files where they are not allowed. + +Copyright (C) 2025 Moko Consulting + +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.Validate +INGROUP: Code.Quality +REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia +PATH: /scripts/validate/tabs.py +VERSION: 01.00.00 +BRIEF: Detect TAB characters in YAML files where they are not allowed +NOTE: YAML specification forbids tab characters +""" + +import subprocess +import sys +from pathlib import Path +from typing import List, 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) + + +def get_yaml_files() -> List[str]: + """ + Get list of YAML files tracked by git. + + Returns: + List of YAML file paths + """ + try: + result = common.run_command( + ["git", "ls-files", "*.yml", "*.yaml"], + capture_output=True, + check=True + ) + files = [f.strip() for f in result.stdout.split('\n') if f.strip()] + return files + except subprocess.CalledProcessError: + return [] + + +def check_tabs_in_file(filepath: str) -> List[Tuple[int, str]]: + """ + Check for tab characters in a file. + + Args: + filepath: Path to file to check + + Returns: + List of (line_number, line_content) tuples with tabs + """ + tabs_found = [] + + try: + with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: + for line_num, line in enumerate(f, 1): + if '\t' in line: + tabs_found.append((line_num, line.rstrip())) + except Exception as e: + common.log_warn(f"Could not read {filepath}: {e}") + + return tabs_found + + +def main() -> int: + """Main entry point.""" + yaml_files = get_yaml_files() + + if not yaml_files: + print("No files to check") + return 0 + + bad_files = [] + all_violations = {} + + for filepath in yaml_files: + tabs = check_tabs_in_file(filepath) + if tabs: + bad_files.append(filepath) + all_violations[filepath] = tabs + + print(f"TAB found in {filepath}", file=sys.stderr) + print(" Lines with tabs:", file=sys.stderr) + + # Show first 5 lines with tabs + for line_num, line_content in tabs[:5]: + print(f" {line_num}: {line_content[:80]}", file=sys.stderr) + + if len(tabs) > 5: + print(f" ... and {len(tabs) - 5} more", file=sys.stderr) + print("", file=sys.stderr) + + if bad_files: + print("", file=sys.stderr) + print("ERROR: Tabs found in repository files", file=sys.stderr) + print("", file=sys.stderr) + print("YAML specification forbids tab characters.", file=sys.stderr) + print(f"Found tabs in {len(bad_files)} file(s):", file=sys.stderr) + for f in bad_files: + print(f" - {f}", file=sys.stderr) + print("", file=sys.stderr) + print("To fix:", file=sys.stderr) + print(" 1. Run: python3 scripts/fix/tabs.py", file=sys.stderr) + print(" 2. Or manually replace tabs with spaces in your editor", file=sys.stderr) + print(" 3. Configure your editor to use spaces (not tabs) for YAML files", file=sys.stderr) + print("", file=sys.stderr) + return 2 + + print("tabs: ok") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) -- 2.49.1 From ef9bf28444ee3ea8cd2610c65daa5ada444dfbf2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:18:51 +0000 Subject: [PATCH 3/8] Update workflows and Makefile to use Python scripts instead of shell scripts Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- .github/workflows/ci.yml | 31 ++-- .github/workflows/deploy_staging.yml | 17 +- .github/workflows/release_pipeline.yml | 47 +++--- Makefile | 23 ++- scripts/validate/no_secrets.py | 0 scripts/validate/paths.py | 169 +++++++++++++++++++ scripts/validate/tabs.py | 0 scripts/validate/workflows.py | 217 +++++++++++++++++++++++++ 8 files changed, 448 insertions(+), 56 deletions(-) mode change 100644 => 100755 scripts/validate/no_secrets.py create mode 100755 scripts/validate/paths.py mode change 100644 => 100755 scripts/validate/tabs.py create mode 100755 scripts/validate/workflows.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b424006..b361e7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,31 +36,36 @@ jobs: run: | git config --global core.autocrlf false + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Verify script executability run: | - chmod +x scripts/**/*.sh || true + chmod +x scripts/**/*.py || true - name: Required validations run: | set -e - scripts/validate/manifest.sh - scripts/validate/xml_wellformed.sh - scripts/validate/workflows.sh + python3 scripts/validate/manifest.py + python3 scripts/validate/xml_wellformed.py + python3 scripts/validate/workflows.py - name: Optional validations run: | set +e - scripts/validate/changelog.sh - scripts/validate/language_structure.sh - scripts/validate/license_headers.sh - scripts/validate/no_secrets.sh - scripts/validate/paths.sh - scripts/validate/php_syntax.sh - scripts/validate/tabs.sh - scripts/validate/version_alignment.sh - scripts/validate/version_hierarchy.sh + python3 scripts/validate/changelog.py || echo "changelog validation not yet converted" + python3 scripts/validate/language_structure.py || echo "language_structure validation not yet converted" + python3 scripts/validate/license_headers.py || echo "license_headers validation not yet converted" + python3 scripts/validate/no_secrets.py + python3 scripts/validate/paths.py + python3 scripts/validate/php_syntax.py + python3 scripts/validate/tabs.py + python3 scripts/validate/version_alignment.py || echo "version_alignment validation not yet converted" + python3 scripts/validate/version_hierarchy.py || echo "version_hierarchy validation not yet converted" - name: CI summary if: always() diff --git a/.github/workflows/deploy_staging.yml b/.github/workflows/deploy_staging.yml index a2c7741..82ce92c 100644 --- a/.github/workflows/deploy_staging.yml +++ b/.github/workflows/deploy_staging.yml @@ -53,26 +53,31 @@ jobs: exit 1 fi + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Run pre-deployment validations run: | - chmod +x scripts/validate/*.sh + chmod +x scripts/validate/*.py # Required validations - scripts/validate/manifest.sh - scripts/validate/xml_wellformed.sh - scripts/validate/php_syntax.sh + python3 scripts/validate/manifest.py + python3 scripts/validate/xml_wellformed.py + python3 scripts/validate/php_syntax.py - name: Build deployment package id: build run: | - chmod +x scripts/release/package_extension.sh + chmod +x scripts/release/package_extension.py VERSION="${{ inputs.version }}" if [ -z "${VERSION}" ]; then VERSION=$(grep -oP '\K[^<]+' src/templates/templateDetails.xml | head -n 1) fi - scripts/release/package_extension.sh dist "${VERSION}" + python3 scripts/release/package_extension.py dist "${VERSION}" ZIP_FILE=$(ls dist/*.zip | head -n 1) echo "package=${ZIP_FILE}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release_pipeline.yml b/.github/workflows/release_pipeline.yml index 52ec397..d83500a 100644 --- a/.github/workflows/release_pipeline.yml +++ b/.github/workflows/release_pipeline.yml @@ -103,21 +103,20 @@ jobs: # Check if REF_NAME is main or matches version pattern if [ "${REF_NAME}" = "main" ]; then # Infer version from manifest when on main branch - SCRIPT_LIB_DIR="${GITHUB_WORKSPACE}/scripts/lib" - if [ ! -f "${SCRIPT_LIB_DIR}/joomla_manifest.sh" ]; then - echo "ERROR: Cannot find joomla_manifest.sh library" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - # Source the library functions - . "${SCRIPT_LIB_DIR}/joomla_manifest.sh" - - # Find and extract version from manifest - MANIFEST="$(find_manifest "${GITHUB_WORKSPACE}/src")" - VERSION="$(get_manifest_version "${MANIFEST}")" + # Use Python library for cross-platform compatibility + VERSION=$(python3 -c " +import sys +sys.path.insert(0, '${GITHUB_WORKSPACE}/scripts/lib') +import extension_utils +ext_info = extension_utils.get_extension_info('${GITHUB_WORKSPACE}/src') +if ext_info: + print(ext_info.version) +else: + sys.exit(1) + ") if [ -z "${VERSION}" ]; then - echo "ERROR: Failed to extract version from manifest: ${MANIFEST}" >> "${GITHUB_STEP_SUMMARY}" + echo "ERROR: Failed to extract version from manifest" >> "${GITHUB_STEP_SUMMARY}" exit 1 fi @@ -552,19 +551,19 @@ jobs: set -euo pipefail required_scripts=( - "scripts/validate/manifest.sh" - "scripts/validate/xml_wellformed.sh" + "scripts/validate/manifest.py" + "scripts/validate/xml_wellformed.py" ) optional_scripts=( - "scripts/validate/changelog.sh" - "scripts/validate/language_structure.sh" - "scripts/validate/license_headers.sh" - "scripts/validate/no_secrets.sh" - "scripts/validate/paths.sh" - "scripts/validate/php_syntax.sh" - "scripts/validate/tabs.sh" - "scripts/validate/version_alignment.sh" + "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" ) missing=() @@ -596,7 +595,7 @@ jobs: for s in "${required_scripts[@]}" "${optional_scripts[@]}"; do if [ -f "${s}" ]; then chmod +x "${s}" - "${s}" >> "${GITHUB_STEP_SUMMARY}" + python3 "${s}" >> "${GITHUB_STEP_SUMMARY}" ran+=("${s}") else skipped+=("${s}") diff --git a/Makefile b/Makefile index 7e302f9..b79bf4a 100644 --- a/Makefile +++ b/Makefile @@ -34,14 +34,14 @@ install: ## validate: Run all validation scripts validate: @echo "Running validation scripts..." - @./scripts/run/validate_all.sh + @python3 ./scripts/run/validate_all.py ## validate-required: Run only required validation scripts validate-required: @echo "Running required validations..." - @./scripts/validate/manifest.sh - @./scripts/validate/xml_wellformed.sh - @./scripts/validate/workflows.sh + @python3 ./scripts/validate/manifest.py + @python3 ./scripts/validate/xml_wellformed.py + @python3 ./scripts/validate/workflows.py @echo "✓ Required validations passed" ## test: Run all tests @@ -96,18 +96,18 @@ phpcompat: ## package: Create distribution package package: @echo "Creating distribution package..." - @./scripts/release/package_extension.sh dist $(VERSION) + @python3 ./scripts/release/package_extension.py dist $(VERSION) @echo "✓ Package created: dist/moko-cassiopeia-$(VERSION)-*.zip" ## smoke-test: Run smoke tests smoke-test: @echo "Running smoke tests..." - @./scripts/run/smoke_test.sh + @python3 ./scripts/run/smoke_test.py ## script-health: Check script health script-health: @echo "Checking script health..." - @./scripts/run/script_health.sh + @python3 ./scripts/run/script_health.py ## version-check: Display current version information version-check: @@ -119,7 +119,7 @@ version-check: ## fix-permissions: Fix script executable permissions fix-permissions: @echo "Fixing script permissions..." - @find scripts -type f -name "*.sh" -exec chmod +x {} \; + @find scripts -type f -name "*.py" -exec chmod +x {} \; @echo "✓ Permissions fixed" ## clean: Remove generated files and caches @@ -174,13 +174,10 @@ watch: ## list-scripts: List all available scripts list-scripts: @echo "Available validation scripts:" - @find scripts/validate -type f -name "*.sh" -exec basename {} \; | sort + @find scripts/validate -type f -name "*.py" -exec basename {} \; | sort @echo "" @echo "Available fix scripts:" - @find scripts/fix -type f -name "*.sh" -exec basename {} \; | sort - @echo "" - @echo "Available run scripts (bash):" - @find scripts/run -type f -name "*.sh" -exec basename {} \; | sort + @find scripts/fix -type f -name "*.py" -exec basename {} \; | sort @echo "" @echo "Available run scripts (python):" @find scripts/run -type f -name "*.py" -exec basename {} \; | sort diff --git a/scripts/validate/no_secrets.py b/scripts/validate/no_secrets.py old mode 100644 new mode 100755 diff --git a/scripts/validate/paths.py b/scripts/validate/paths.py new file mode 100755 index 0000000..8fadf1d --- /dev/null +++ b/scripts/validate/paths.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +""" +Detect Windows-style path separators (backslashes). + +Copyright (C) 2025 Moko Consulting + +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.Validate +INGROUP: Path.Normalization +REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia +PATH: /scripts/validate/paths.py +VERSION: 01.00.00 +BRIEF: Detect Windows-style path separators (backslashes) +NOTE: Ensures cross-platform path compatibility +""" + +import mimetypes +import subprocess +import sys +from pathlib import Path +from typing import List, Tuple, Dict + +# 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) + + +def get_tracked_files() -> List[str]: + """ + Get list of files tracked by git. + + Returns: + List of file paths + """ + try: + result = common.run_command( + ["git", "ls-files", "-z"], + capture_output=True, + check=True + ) + files = [f for f in result.stdout.split('\0') if f.strip()] + return files + except subprocess.CalledProcessError: + return [] + + +def is_binary_file(filepath: str) -> bool: + """ + Check if a file is likely binary. + + Args: + filepath: Path to file + + Returns: + True if likely binary + """ + # Check mime type + mime_type, _ = mimetypes.guess_type(filepath) + if mime_type and mime_type.startswith(('application/', 'audio/', 'image/', 'video/')): + return True + + # Check for null bytes (heuristic for binary files) + try: + with open(filepath, 'rb') as f: + chunk = f.read(1024) + if b'\x00' in chunk: + return True + except Exception: + return True + + return False + + +def find_backslashes_in_file(filepath: str) -> List[Tuple[int, str]]: + """ + Find lines with backslashes in a file. + + Args: + filepath: Path to file + + Returns: + List of (line_number, line_content) tuples + """ + backslashes = [] + + try: + with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: + for line_num, line in enumerate(f, 1): + if '\\' in line: + backslashes.append((line_num, line.rstrip())) + except Exception as e: + common.log_warn(f"Could not read {filepath}: {e}") + + return backslashes + + +def main() -> int: + """Main entry point.""" + tracked_files = get_tracked_files() + + if not tracked_files: + print("No files to check") + return 0 + + hits: Dict[str, List[Tuple[int, str]]] = {} + + for filepath in tracked_files: + # Skip binary files + if is_binary_file(filepath): + continue + + # Find backslashes + backslashes = find_backslashes_in_file(filepath) + if backslashes: + hits[filepath] = backslashes + + if hits: + print("ERROR: Windows-style path literals detected", file=sys.stderr) + print("", file=sys.stderr) + print(f"Found backslashes in {len(hits)} file(s):", file=sys.stderr) + + for filepath, lines in hits.items(): + print("", file=sys.stderr) + print(f" File: {filepath}", file=sys.stderr) + print(" Lines with backslashes:", file=sys.stderr) + + # Show first 5 lines + for line_num, line_content in lines[:5]: + print(f" {line_num}: {line_content[:80]}", file=sys.stderr) + + if len(lines) > 5: + print(f" ... and {len(lines) - 5} more", file=sys.stderr) + + print("", file=sys.stderr) + print("To fix:", file=sys.stderr) + print(" 1. Run: python3 scripts/fix/paths.py", file=sys.stderr) + print(" 2. Or manually replace backslashes (\\) with forward slashes (/)", file=sys.stderr) + print(" 3. Ensure paths use POSIX separators for cross-platform compatibility", file=sys.stderr) + print("", file=sys.stderr) + return 2 + + print("paths: ok") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/validate/tabs.py b/scripts/validate/tabs.py old mode 100644 new mode 100755 diff --git a/scripts/validate/workflows.py b/scripts/validate/workflows.py new file mode 100755 index 0000000..9473a9f --- /dev/null +++ b/scripts/validate/workflows.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +""" +Validate GitHub Actions workflow files. + +Copyright (C) 2025 Moko Consulting + +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.Validate +INGROUP: CI.Validation +REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia +PATH: /scripts/validate/workflows.py +VERSION: 01.00.00 +BRIEF: Validate GitHub Actions workflow files +NOTE: Checks YAML syntax, structure, and best practices +""" + +import sys +from pathlib import Path +from typing import List, 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) + + +def validate_yaml_syntax(filepath: Path) -> bool: + """ + Validate YAML syntax of a workflow file. + + Args: + filepath: Path to workflow file + + Returns: + True if valid + """ + try: + import yaml + except ImportError: + common.log_warn("PyYAML module not installed. Install with: pip3 install pyyaml") + return True # Skip validation if yaml not available + + try: + with open(filepath, 'r', encoding='utf-8') as f: + yaml.safe_load(f) + print(f"✓ Valid YAML: {filepath.name}") + return True + except yaml.YAMLError as e: + print(f"✗ YAML Error in {filepath.name}: {e}", file=sys.stderr) + return False + except Exception as e: + print(f"✗ Error reading {filepath.name}: {e}", file=sys.stderr) + return False + + +def check_no_tabs(filepath: Path) -> bool: + """ + Check that file contains no tab characters. + + Args: + filepath: Path to file + + Returns: + True if no tabs found + """ + try: + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + if '\t' in content: + common.log_error(f"✗ File contains tab characters: {filepath.name}") + return False + except Exception as e: + common.log_warn(f"Could not read {filepath}: {e}") + return False + + return True + + +def check_workflow_structure(filepath: Path) -> bool: + """ + Check workflow file structure for required keys. + + Args: + filepath: Path to workflow file + + Returns: + True if structure is valid + """ + errors = 0 + + try: + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Check for required top-level keys + if 'name:' not in content and not content.startswith('name:'): + common.log_warn(f"Missing 'name:' in {filepath.name}") + + if 'on:' not in content and not content.startswith('on:'): + common.log_error(f"✗ Missing 'on:' trigger in {filepath.name}") + errors += 1 + + if 'jobs:' not in content and not content.startswith('jobs:'): + common.log_error(f"✗ Missing 'jobs:' in {filepath.name}") + errors += 1 + + except Exception as e: + common.log_error(f"Error reading {filepath}: {e}") + return False + + return errors == 0 + + +def validate_workflow_file(filepath: Path) -> bool: + """ + Validate a single workflow file. + + Args: + filepath: Path to workflow file + + Returns: + True if valid + """ + common.log_info(f"Validating: {filepath.name}") + + errors = 0 + + # Check YAML syntax + if not validate_yaml_syntax(filepath): + errors += 1 + + # Check for tabs + if not check_no_tabs(filepath): + errors += 1 + + # Check structure + if not check_workflow_structure(filepath): + errors += 1 + + if errors == 0: + common.log_info(f"✓ {filepath.name} passed all checks") + return True + else: + common.log_error(f"✗ {filepath.name} failed {errors} check(s)") + return False + + +def main() -> int: + """Main entry point.""" + common.log_info("GitHub Actions Workflow Validation") + common.log_info("===================================") + print() + + workflows_dir = Path(".github/workflows") + + if not workflows_dir.is_dir(): + common.log_error(f"Workflows directory not found: {workflows_dir}") + return 1 + + # Find all workflow files + workflow_files = [] + for pattern in ["*.yml", "*.yaml"]: + workflow_files.extend(workflows_dir.glob(pattern)) + + if not workflow_files: + common.log_warn("No workflow files found") + return 0 + + total = len(workflow_files) + passed = 0 + failed = 0 + + for workflow in workflow_files: + if validate_workflow_file(workflow): + passed += 1 + else: + failed += 1 + print() + + common.log_info("===================================") + common.log_info("Summary:") + common.log_info(f" Total workflows: {total}") + common.log_info(f" Passed: {passed}") + common.log_info(f" Failed: {failed}") + common.log_info("===================================") + + if failed > 0: + common.log_error("Workflow validation failed") + return 1 + + common.log_info("All workflows validated successfully") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) -- 2.49.1 From c471225a93f1893ae49b0456fc16369747ecda77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:20:19 +0000 Subject: [PATCH 4/8] Add validate_all.py and update package_extension.py for Dolibarr support Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- scripts/release/package_extension.py | 31 +++-- scripts/run/validate_all.py | 181 +++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 12 deletions(-) create mode 100755 scripts/run/validate_all.py diff --git a/scripts/release/package_extension.py b/scripts/release/package_extension.py index e1fb1fa..d23727a 100755 --- a/scripts/release/package_extension.py +++ b/scripts/release/package_extension.py @@ -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: 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), "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. """ ) diff --git a/scripts/run/validate_all.py b/scripts/run/validate_all.py new file mode 100755 index 0000000..79657bb --- /dev/null +++ b/scripts/run/validate_all.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +""" +Run all validation scripts. + +Copyright (C) 2025 Moko Consulting + +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 + +# 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", +] + + +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()) -- 2.49.1 From 09989a386cdb62575efedb255e8e0d6c566d158e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:21:25 +0000 Subject: [PATCH 5/8] Delete all shell script files, keeping only Python versions Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- scripts/fix/paths.sh | 75 ------- scripts/fix/tabs.sh | 73 ------- scripts/fix/versions.sh | 177 --------------- scripts/git/install-hooks.sh | 56 ----- scripts/git/pre-commit.sh | 272 ----------------------- scripts/lib/common.sh | 233 -------------------- scripts/lib/joomla_manifest.sh | 193 ---------------- scripts/lib/logging.sh | 118 ---------- scripts/release/package_extension.sh | 213 ------------------ scripts/release/update_changelog.sh | 122 ----------- scripts/release/update_dates.sh | 131 ----------- scripts/run/check_version.sh | 191 ---------------- scripts/run/list_versions.sh | 127 ----------- scripts/run/migrate_unreleased.sh | 292 ------------------------- scripts/run/script_health.sh | 198 ----------------- scripts/run/smoke_test.sh | 194 ---------------- scripts/run/validate_all.sh | 225 ------------------- scripts/validate/changelog.sh | 208 ------------------ scripts/validate/language_structure.sh | 119 ---------- scripts/validate/license_headers.sh | 96 -------- scripts/validate/manifest.sh | 247 --------------------- scripts/validate/no_secrets.sh | 86 -------- scripts/validate/paths.sh | 78 ------- scripts/validate/php_syntax.sh | 113 ---------- scripts/validate/tabs.sh | 87 -------- scripts/validate/version_alignment.sh | 100 --------- scripts/validate/version_hierarchy.sh | 134 ------------ scripts/validate/workflows.sh | 191 ---------------- scripts/validate/xml_wellformed.sh | 91 -------- 29 files changed, 4440 deletions(-) delete mode 100755 scripts/fix/paths.sh delete mode 100755 scripts/fix/tabs.sh delete mode 100755 scripts/fix/versions.sh delete mode 100755 scripts/git/install-hooks.sh delete mode 100755 scripts/git/pre-commit.sh delete mode 100755 scripts/lib/common.sh delete mode 100755 scripts/lib/joomla_manifest.sh delete mode 100755 scripts/lib/logging.sh delete mode 100755 scripts/release/package_extension.sh delete mode 100755 scripts/release/update_changelog.sh delete mode 100755 scripts/release/update_dates.sh delete mode 100755 scripts/run/check_version.sh delete mode 100755 scripts/run/list_versions.sh delete mode 100755 scripts/run/migrate_unreleased.sh delete mode 100755 scripts/run/script_health.sh delete mode 100755 scripts/run/smoke_test.sh delete mode 100755 scripts/run/validate_all.sh delete mode 100755 scripts/validate/changelog.sh delete mode 100755 scripts/validate/language_structure.sh delete mode 100755 scripts/validate/license_headers.sh delete mode 100755 scripts/validate/manifest.sh delete mode 100755 scripts/validate/no_secrets.sh delete mode 100755 scripts/validate/paths.sh delete mode 100755 scripts/validate/php_syntax.sh delete mode 100755 scripts/validate/tabs.sh delete mode 100755 scripts/validate/version_alignment.sh delete mode 100755 scripts/validate/version_hierarchy.sh delete mode 100755 scripts/validate/workflows.sh delete mode 100755 scripts/validate/xml_wellformed.sh diff --git a/scripts/fix/paths.sh b/scripts/fix/paths.sh deleted file mode 100755 index d4f91bf..0000000 --- a/scripts/fix/paths.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -# ----------------------------------------------------------------------------- -# Copyright (C) 2025 Moko Consulting -# -# 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 (./LICENSE). -# ----------------------------------------------------------------------------- -# FILE INFORMATION -# DEFGROUP: MokoStandards -# INGROUP: Generic.Script -# REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /scripts/fix/paths.sh -# VERSION: 01.00.00 -# BRIEF: Replace Windows-style path separators with POSIX separators in text files.# -# Purpose: -# - Normalize path separators in text files to forward slashes (/). -# - Intended for CI validation and optional remediation workflows. -# - Skips binary files and version control metadata. -# - Preserves file contents aside from path separator normalization. -# -# Usage: -# ./scripts/fix/paths.sh -# ============================================================================= - -set -euo pipefail - -ROOT_DIR="${1:-.}" - -info() { - echo "INFO: $*" -} - -warn() { - echo "WARN: $*" 1>&2 -} - -die() { - echo "ERROR: $*" 1>&2 - exit 1 -} - -command -v find >/dev/null 2>&1 || die "find not available" -command -v sed >/dev/null 2>&1 || die "sed not available" -command -v file >/dev/null 2>&1 || die "file not available" - -info "Scanning for text files under: $ROOT_DIR" - -while IFS= read -r -d '' file; do - if file "$file" | grep -qi "text"; then - if grep -q '\\\\' "$file"; then - sed -i.bak 's#\\\\#/#g' "$file" && rm -f "$file.bak" - info "Normalized paths in $file" - fi - fi -done < <( - find "$ROOT_DIR" \ - -type f \ - -not -path "*/.git/*" \ - -not -path "*/node_modules/*" \ - -print0 -) - -info "Path normalization complete." diff --git a/scripts/fix/tabs.sh b/scripts/fix/tabs.sh deleted file mode 100755 index dd28f88..0000000 --- a/scripts/fix/tabs.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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. If not, see . -# -# FILE INFORMATION -# DEFGROUP: MokoStandards -# INGROUP: GitHub.Actions.Utilities -# REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /scripts/fix/tabs.sh -# VERSION: 01.00.00 -# BRIEF: Utility script to replace tab characters with two spaces in YAML files. -# NOTE: Intended for local developer use. Not executed automatically in CI. -# ============================================================================ - -set -euo pipefail - -log() { - printf '%s\n' "$*" -} - -log "[fix/tabs] Scope: *.yml, *.yaml" -log "[fix/tabs] Action: replace tab characters with two spaces" - -changed=0 - -# Determine file list -if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - files=$(git ls-files '*.yml' '*.yaml' 2>/dev/null || true) -else - files=$(find . -type f \( -name '*.yml' -o -name '*.yaml' \) -print 2>/dev/null || true) -fi - -if [ -z "${files}" ]; then - log "[fix/tabs] No YAML files found. Nothing to fix." - exit 0 -fi - -while IFS= read -r f; do - [ -n "$f" ] || continue - [ -f "$f" ] || continue - - if LC_ALL=C grep -q $'\t' -- "$f"; then - log "[fix/tabs] Fixing tabs in: $f" - # Replace literal tab characters with exactly two spaces - sed -i 's/\t/ /g' "$f" - changed=1 - else - log "[fix/tabs] Clean: $f" - fi -done <<< "${files}" - -if [ "$changed" -eq 1 ]; then - log "[fix/tabs] Completed with modifications" -else - log "[fix/tabs] No changes required" -fi diff --git a/scripts/fix/versions.sh b/scripts/fix/versions.sh deleted file mode 100755 index b4c42b3..0000000 --- a/scripts/fix/versions.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Fix -# INGROUP: Version.Management -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/fix/versions.sh -# VERSION: 01.00.00 -# BRIEF: Update version numbers across repository files -# NOTE: Updates manifest, package.json, and other version references -# ============================================================================ - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# ---------------------------------------------------------------------------- -# Usage and validation -# ---------------------------------------------------------------------------- - -usage() { -cat <<-USAGE -Usage: $0 - -Update version numbers across repository files. - -Arguments: - VERSION Semantic version in format X.Y.Z (e.g., 3.5.0) - -Examples: - $0 3.5.0 - $0 1.2.3 - -Exit codes: - 0 - Version updated successfully - 1 - Invalid version format or update failed - 2 - Invalid arguments - -Files updated: - - Joomla manifest XML ( tag) - - package.json (if present) - - README.md (VERSION: references, if present) - -USAGE -exit 0 -} - -validate_version() { -local v="$1" -if ! printf '%s' "$v" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then -die "Invalid version format: $v (expected X.Y.Z)" -fi -} - -# ---------------------------------------------------------------------------- -# Main -# ---------------------------------------------------------------------------- - -# Parse arguments -if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then - usage -fi - -[ $# -eq 1 ] || usage - -VERSION="$1" -validate_version "${VERSION}" - -# Check dependencies -check_dependencies python3 - -log_info "Updating version to: ${VERSION}" -log_info "Start time: $(log_timestamp)" - -# Source Joomla manifest utilities -. "${SCRIPT_DIR}/lib/joomla_manifest.sh" - -# Find and update manifest -MANIFEST="$(find_manifest src)" -log_info "Updating manifest: ${MANIFEST}" - -# Cross-platform sed helper -sed_inplace() { -local expr="$1" -local file="$2" - -if sed --version >/dev/null 2>&1; then -sed -i -E "${expr}" "${file}" -else -sed -i '' -E "${expr}" "${file}" -fi -} - -# Update version in manifest XML -if grep -q '' "${MANIFEST}"; then -sed_inplace "s|[^<]*|${VERSION}|g" "${MANIFEST}" -log_info "✓ Updated manifest version" -else -log_warn "No tag found in manifest" -fi - -# Update package.json if it exists -if [ -f "package.json" ]; then -if command -v python3 >/dev/null 2>&1; then -python3 - < -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Moko-Cassiopeia.Scripts -# INGROUP: Scripts.Git -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# FILE: ./scripts/git/install-hooks.sh -# VERSION: 01.00.00 -# BRIEF: Install Git hooks for local development - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -echo "Installing Git hooks..." -echo "" - -# Create .git/hooks directory if it doesn't exist -mkdir -p "${REPO_ROOT}/.git/hooks" - -# Install pre-commit hook -PRE_COMMIT_HOOK="${REPO_ROOT}/.git/hooks/pre-commit" -cat > "${PRE_COMMIT_HOOK}" <<'EOF' -#!/usr/bin/env bash -# Pre-commit hook - installed by scripts/git/install-hooks.sh - -SCRIPT_DIR="$(git rev-parse --show-toplevel)/scripts/git" - -if [ -f "${SCRIPT_DIR}/pre-commit.sh" ]; then - exec "${SCRIPT_DIR}/pre-commit.sh" "$@" -else - echo "Error: pre-commit.sh not found in ${SCRIPT_DIR}" - exit 1 -fi -EOF - -chmod +x "${PRE_COMMIT_HOOK}" - -echo "✓ Installed pre-commit hook" -echo "" -echo "The pre-commit hook will run automatically before each commit." -echo "" -echo "Options:" -echo " - Skip hook: git commit --no-verify" -echo " - Quick mode: ./scripts/git/pre-commit.sh --quick" -echo " - Skip quality checks: ./scripts/git/pre-commit.sh --skip-quality" -echo "" -echo "To uninstall hooks:" -echo " rm .git/hooks/pre-commit" -echo "" -echo "Done!" diff --git a/scripts/git/pre-commit.sh b/scripts/git/pre-commit.sh deleted file mode 100755 index 673548c..0000000 --- a/scripts/git/pre-commit.sh +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/env bash -# Pre-commit hook script for Moko Cassiopeia -# Copyright (C) 2025 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Moko-Cassiopeia.Scripts -# INGROUP: Scripts.Git -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# FILE: ./scripts/git/pre-commit.sh -# VERSION: 01.00.00 -# BRIEF: Pre-commit hook for local validation - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -log_success() { - echo -e "${GREEN}✓${NC} $*" -} - -log_warning() { - echo -e "${YELLOW}⚠${NC} $*" -} - -log_error() { - echo -e "${RED}✗${NC} $*" -} - -log_header() { - echo "" - echo "================================" - echo "$*" - echo "================================" -} - -# Parse arguments -SKIP_TESTS=false -SKIP_QUALITY=false -QUICK_MODE=false - -while [[ $# -gt 0 ]]; do - case $1 in - --skip-tests) - SKIP_TESTS=true - shift - ;; - --skip-quality) - SKIP_QUALITY=true - shift - ;; - --quick) - QUICK_MODE=true - shift - ;; - *) - echo "Unknown option: $1" - echo "Usage: pre-commit.sh [--skip-tests] [--skip-quality] [--quick]" - exit 1 - ;; - esac -done - -log_header "Pre-commit Validation" - -# Get list of staged files -STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR) - -if [ -z "$STAGED_FILES" ]; then - log_warning "No staged files to check" - exit 0 -fi - -echo "Checking staged files:" -echo "$STAGED_FILES" | sed 's/^/ - /' -echo "" - -# Track failures -FAILURES=0 - -# Check 1: PHP Syntax -log_header "Checking PHP Syntax" -PHP_FILES=$(echo "$STAGED_FILES" | grep '\.php$' || true) - -if [ -n "$PHP_FILES" ]; then - while IFS= read -r file; do - if [ -f "$file" ]; then - if php -l "$file" > /dev/null 2>&1; then - log_success "PHP syntax OK: $file" - else - log_error "PHP syntax error: $file" - php -l "$file" - FAILURES=$((FAILURES + 1)) - fi - fi - done <<< "$PHP_FILES" -else - echo " No PHP files to check" -fi - -# Check 2: XML Well-formedness -log_header "Checking XML Files" -XML_FILES=$(echo "$STAGED_FILES" | grep '\.xml$' || true) - -if [ -n "$XML_FILES" ]; then - while IFS= read -r file; do - if [ -f "$file" ]; then - if xmllint --noout "$file" 2>/dev/null; then - log_success "XML well-formed: $file" - else - log_error "XML malformed: $file" - xmllint --noout "$file" || true - FAILURES=$((FAILURES + 1)) - fi - fi - done <<< "$XML_FILES" -else - echo " No XML files to check" -fi - -# Check 3: YAML Syntax -log_header "Checking YAML Files" -YAML_FILES=$(echo "$STAGED_FILES" | grep -E '\.(yml|yaml)$' || true) - -if [ -n "$YAML_FILES" ]; then - while IFS= read -r file; do - if [ -f "$file" ]; then - # Use printf to safely pass the file path, avoiding injection - if python3 -c "import sys, yaml; yaml.safe_load(open(sys.argv[1]))" "$file" 2>/dev/null; then - log_success "YAML valid: $file" - else - log_error "YAML invalid: $file" - python3 -c "import sys, yaml; yaml.safe_load(open(sys.argv[1]))" "$file" || true - FAILURES=$((FAILURES + 1)) - fi - fi - done <<< "$YAML_FILES" -else - echo " No YAML files to check" -fi - -# Check 4: Trailing Whitespace -log_header "Checking for Trailing Whitespace" -TEXT_FILES=$(echo "$STAGED_FILES" | grep -vE '\.(png|jpg|jpeg|gif|svg|ico|zip|gz|woff|woff2|ttf)$' || true) - -if [ -n "$TEXT_FILES" ]; then - TRAILING_WS=$(echo "$TEXT_FILES" | xargs grep -n '[[:space:]]$' 2>/dev/null || true) - if [ -n "$TRAILING_WS" ]; then - log_warning "Files with trailing whitespace found:" - echo "$TRAILING_WS" | sed 's/^/ /' - echo "" - echo " Run: sed -i 's/[[:space:]]*$//' to fix" - else - log_success "No trailing whitespace" - fi -else - echo " No text files to check" -fi - -# Check 5: SPDX License Headers (if not quick mode) -if [ "$QUICK_MODE" = false ]; then - log_header "Checking SPDX License Headers" - SOURCE_FILES=$(echo "$STAGED_FILES" | grep -E '\.(php|sh|js|ts|css)$' || true) - - if [ -n "$SOURCE_FILES" ]; then - MISSING_SPDX="" - while IFS= read -r file; do - if [ -f "$file" ]; then - if ! head -n 20 "$file" | grep -q 'SPDX-License-Identifier:'; then - MISSING_SPDX="${MISSING_SPDX} - ${file}\n" - fi - fi - done <<< "$SOURCE_FILES" - - if [ -n "$MISSING_SPDX" ]; then - log_warning "Files missing SPDX license header:" - echo -e "$MISSING_SPDX" - else - log_success "All source files have SPDX headers" - fi - else - echo " No source files to check" - fi -fi - -# Check 6: No Secrets -log_header "Checking for Secrets" -if [ -x "${SCRIPT_DIR}/validate/no_secrets.sh" ]; then - if "${SCRIPT_DIR}/validate/no_secrets.sh" > /dev/null 2>&1; then - log_success "No secrets detected" - else - log_error "Potential secrets detected!" - "${SCRIPT_DIR}/validate/no_secrets.sh" || true - FAILURES=$((FAILURES + 1)) - fi -else - echo " Secret scanner not available" -fi - -# Check 7: PHP_CodeSniffer (if not skipped) -if [ "$SKIP_QUALITY" = false ] && command -v phpcs >/dev/null 2>&1; then - log_header "Running PHP_CodeSniffer" - PHP_FILES=$(echo "$STAGED_FILES" | grep '\.php$' || true) - - if [ -n "$PHP_FILES" ]; then - # Use process substitution to avoid issues with filenames containing spaces - if echo "$PHP_FILES" | tr '\n' '\0' | xargs -0 phpcs --standard=phpcs.xml -q 2>/dev/null; then - log_success "PHPCS passed" - else - log_warning "PHPCS found issues (non-blocking)" - echo "$PHP_FILES" | tr '\n' '\0' | xargs -0 phpcs --standard=phpcs.xml --report=summary || true - fi - else - echo " No PHP files to check" - fi -else - if [ "$SKIP_QUALITY" = true ]; then - echo " Skipping PHPCS (--skip-quality)" - else - echo " PHPCS not available (install with: composer global require squizlabs/php_codesniffer)" - fi -fi - -# Check 8: PHPStan (if not skipped and not quick mode) -if [ "$SKIP_QUALITY" = false ] && [ "$QUICK_MODE" = false ] && command -v phpstan >/dev/null 2>&1; then - log_header "Running PHPStan" - PHP_FILES=$(echo "$STAGED_FILES" | grep '\.php$' || true) - - if [ -n "$PHP_FILES" ]; then - if phpstan analyse --configuration=phpstan.neon --no-progress > /dev/null 2>&1; then - log_success "PHPStan passed" - else - log_warning "PHPStan found issues (non-blocking)" - phpstan analyse --configuration=phpstan.neon --no-progress || true - fi - else - echo " No PHP files to check" - fi -else - if [ "$SKIP_QUALITY" = true ]; then - echo " Skipping PHPStan (--skip-quality)" - elif [ "$QUICK_MODE" = true ]; then - echo " Skipping PHPStan (--quick mode)" - else - echo " PHPStan not available (install with: composer global require phpstan/phpstan)" - fi -fi - -# Summary -log_header "Pre-commit Summary" - -if [ $FAILURES -gt 0 ]; then - log_error "Pre-commit checks failed with $FAILURES error(s)" - echo "" - echo "To commit anyway, use: git commit --no-verify" - echo "To run quick checks only: ./scripts/git/pre-commit.sh --quick" - echo "To skip quality checks: ./scripts/git/pre-commit.sh --skip-quality" - exit 1 -else - log_success "All pre-commit checks passed!" - echo "" - echo "Tip: Use 'make validate' for comprehensive validation" - exit 0 -fi diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh deleted file mode 100755 index ea365f4..0000000 --- a/scripts/lib/common.sh +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/env sh - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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: Common -# REPO: https://github.com/mokoconsulting-tech -# PATH: /scripts/lib/common.sh -# VERSION: 01.00.00 -# BRIEF: Unified shared shell utilities for all CI and local scripts -# NOTE: -# ============================================================================ - -set -eu - -# ---------------------------------------------------------------------------- -# Environment normalization -# ---------------------------------------------------------------------------- - -export LC_ALL=C -export LANG=C - -is_ci() { - [ "${CI:-}" = "true" ] -} - -require_cmd() { - command -v "$1" >/dev/null 2>&1 || { - printf '%s\n' "ERROR: Required command not found: $1" >&2 - exit 1 - } -} - -# ---------------------------------------------------------------------------- -# Logging -# ---------------------------------------------------------------------------- - -log_info() { - printf '%s\n' "INFO: $*" -} - -log_warn() { - printf '%s\n' "WARN: $*" >&2 -} - -log_error() { - printf '%s\n' "ERROR: $*" >&2 -} - -die() { - log_error "$*" - if [ "${VERBOSE_ERRORS:-true}" = "true" ]; then - echo "" >&2 - echo "Stack trace (last 10 commands):" >&2 - if [ -n "${BASH_VERSION:-}" ]; then - history | tail -10 >&2 2>/dev/null || true - fi - echo "" >&2 - echo "Environment:" >&2 - echo " PWD: $(pwd)" >&2 - echo " USER: ${USER:-unknown}" >&2 - echo " SHELL: ${SHELL:-unknown}" >&2 - echo " CI: ${CI:-false}" >&2 - echo "" >&2 - fi - exit 1 -} - -# ---------------------------------------------------------------------------- -# Validation helpers -# ---------------------------------------------------------------------------- - -assert_file_exists() { - [ -f "$1" ] || die "Required file missing: $1" -} - -assert_dir_exists() { - [ -d "$1" ] || die "Required directory missing: $1" -} - -assert_non_empty() { - [ -n "${1:-}" ] || die "Expected non empty value" -} - -# ---------------------------------------------------------------------------- -# Path helpers -# ---------------------------------------------------------------------------- - -script_root() { - cd "$(dirname "$0")/.." && pwd -} - -normalize_path() { - printf '%s\n' "$1" | sed 's|\\|/|g' -} - -# ---------------------------------------------------------------------------- -# JSON utilities -# ---------------------------------------------------------------------------- - -json_escape() { - require_cmd python3 - python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1" -} - -json_output() { - local status="$1" - shift - require_cmd python3 - python3 - </dev/null 2>&1; then - log_error "Required command not found: $cmd" - missing=$((missing + 1)) - missing_cmds+=("$cmd") - fi - done - - if [ "$missing" -gt 0 ]; then - echo "" >&2 - echo "Missing required dependencies:" >&2 - for cmd in "${missing_cmds[@]}"; do - echo " - $cmd" >&2 - done - echo "" >&2 - echo "Installation guides:" >&2 - for cmd in "${missing_cmds[@]}"; do - case "$cmd" in - python3) - echo " python3: apt-get install python3 (Debian/Ubuntu) or brew install python3 (macOS)" >&2 - ;; - git) - echo " git: apt-get install git (Debian/Ubuntu) or brew install git (macOS)" >&2 - ;; - php) - echo " php: apt-get install php-cli (Debian/Ubuntu) or brew install php (macOS)" >&2 - ;; - xmllint) - echo " xmllint: apt-get install libxml2-utils (Debian/Ubuntu) or brew install libxml2 (macOS)" >&2 - ;; - *) - echo " $cmd: Please install via your system package manager" >&2 - ;; - esac - done - echo "" >&2 - die "Missing $missing required command(s)" - fi -} - -# Timeout wrapper for long-running commands -run_with_timeout() { - local timeout="$1" - shift - if command -v timeout >/dev/null 2>&1; then - timeout "$timeout" "$@" - else - "$@" - fi -} - -# Add script execution timestamp -log_timestamp() { - if command -v date >/dev/null 2>&1; then - printf '%s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" - fi -} - -# Calculate and log execution duration -log_duration() { - local start="$1" - local end="$2" - local duration=$((end - start)) - if [ "$duration" -ge 60 ]; then - local minutes=$((duration / 60)) - local seconds=$((duration % 60)) - printf '%dm %ds\n' "$minutes" "$seconds" - else - printf '%ds\n' "$duration" - fi -} diff --git a/scripts/lib/joomla_manifest.sh b/scripts/lib/joomla_manifest.sh deleted file mode 100755 index abbb14e..0000000 --- a/scripts/lib/joomla_manifest.sh +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env sh - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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: Joomla.Manifest -# REPO: https://github.com/mokoconsulting-tech -# PATH: /scripts/lib/joomla_manifest.sh -# VERSION: 01.00.00 -# BRIEF: Joomla manifest parsing and validation utilities -# NOTE: Provides reusable functions for working with Joomla extension manifests -# ============================================================================ - -set -eu - -# Resolve script directory properly - works when sourced -if [ -n "${SCRIPT_DIR:-}" ]; then - # Already set by caller - SCRIPT_LIB_DIR="${SCRIPT_DIR}/lib" -else - # Determine from this file's location - SCRIPT_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" -fi - -# Shared utilities -. "${SCRIPT_LIB_DIR}/common.sh" - -# ---------------------------------------------------------------------------- -# Manifest discovery -# ---------------------------------------------------------------------------- - -# Find the primary Joomla manifest in the given directory -# Usage: find_manifest -# Returns: path to manifest file or exits with error -find_manifest() { -local src_dir="${1:-src}" - -[ -d "${src_dir}" ] || die "Source directory missing: ${src_dir}" - -# Candidate discovery policy: prefer explicit known names -local candidates="" - -# Template -if [ -f "${src_dir}/templateDetails.xml" ]; then -candidates="${src_dir}/templateDetails.xml" -fi - -# Package -if [ -z "${candidates}" ]; then -candidates="$(find "${src_dir}" -maxdepth 4 -type f -name 'pkg_*.xml' 2>/dev/null | head -1 || true)" -fi - -# Component -if [ -z "${candidates}" ]; then -candidates="$(find "${src_dir}" -maxdepth 4 -type f -name 'com_*.xml' 2>/dev/null | head -1 || true)" -fi - -# Module -if [ -z "${candidates}" ]; then -candidates="$(find "${src_dir}" -maxdepth 4 -type f -name 'mod_*.xml' 2>/dev/null | head -1 || true)" -fi - -# Plugin -if [ -z "${candidates}" ]; then -candidates="$(find "${src_dir}" -maxdepth 6 -type f -name 'plg_*.xml' 2>/dev/null | head -1 || true)" -fi - -# Fallback: any XML containing -if [ -z "${candidates}" ]; then -candidates="$(grep -Rsl --include='*.xml' '/dev/null | head -1 || true)" -fi - -[ -n "${candidates}" ] || die "No Joomla manifest XML found under ${src_dir}" -[ -s "${candidates}" ] || die "Manifest is empty: ${candidates}" - -printf '%s\n' "${candidates}" -} - -# ---------------------------------------------------------------------------- -# Manifest parsing -# ---------------------------------------------------------------------------- - -# Extract version from manifest XML -# Usage: get_manifest_version -# Returns: version string or exits with error -get_manifest_version() { - local manifest="$1" - - [ -f "${manifest}" ] || die "Manifest not found: ${manifest}" - - require_cmd python3 - - python3 - "${manifest}" <<'PY' -import sys -import xml.etree.ElementTree as ET - -manifest_path = sys.argv[1] - -try: - tree = ET.parse(manifest_path) - root = tree.getroot() - version_el = root.find("version") - if version_el is not None and version_el.text: - print(version_el.text.strip()) - sys.exit(0) -except Exception: - pass - -sys.exit(1) -PY -} - -# Extract extension name from manifest XML -# Usage: get_manifest_name -# Returns: name string or exits with error -get_manifest_name() { - local manifest="$1" - - [ -f "${manifest}" ] || die "Manifest not found: ${manifest}" - - require_cmd python3 - - python3 - "${manifest}" <<'PY' -import sys -import xml.etree.ElementTree as ET - -manifest_path = sys.argv[1] - -try: - tree = ET.parse(manifest_path) - root = tree.getroot() - name_el = root.find("name") - if name_el is not None and name_el.text: - print(name_el.text.strip()) - sys.exit(0) -except Exception: - pass - -sys.exit(1) -PY -} - -# Extract extension type from manifest XML -# Usage: get_manifest_type -# Returns: type string (template, component, module, plugin, etc.) or exits with error -get_manifest_type() { - local manifest="$1" - - [ -f "${manifest}" ] || die "Manifest not found: ${manifest}" - - require_cmd python3 - - python3 - "${manifest}" <<'PY' -import sys -import xml.etree.ElementTree as ET - -manifest_path = sys.argv[1] - -try: - tree = ET.parse(manifest_path) - root = tree.getroot() - ext_type = root.attrib.get("type", "").strip().lower() - if ext_type: - print(ext_type) - sys.exit(0) -except Exception: - pass - -sys.exit(1) -PY -} diff --git a/scripts/lib/logging.sh b/scripts/lib/logging.sh deleted file mode 100755 index e2e4cb2..0000000 --- a/scripts/lib/logging.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env sh - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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: Logging -# REPO: https://github.com/mokoconsulting-tech -# PATH: /scripts/lib/logging.sh -# VERSION: 01.00.00 -# BRIEF: Enhanced logging utilities with structured output support -# NOTE: Provides colored output, log levels, and structured logging -# ============================================================================ - -set -eu - -# Resolve script directory properly - works when sourced -if [ -n "${SCRIPT_DIR:-}" ]; then - # Already set by caller - SCRIPT_LIB_DIR="${SCRIPT_DIR}/lib" -else - # Determine from this file's location - SCRIPT_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" -fi - -# Shared utilities -. "${SCRIPT_LIB_DIR}/common.sh" - -# ---------------------------------------------------------------------------- -# Color codes (if terminal supports it) -# ---------------------------------------------------------------------------- - -# Check if we're in a terminal and colors are supported -use_colors() { - [ -t 1 ] && [ "${CI:-false}" != "true" ] -} - -if use_colors; then - COLOR_RESET='\033[0m' - COLOR_RED='\033[0;31m' - COLOR_YELLOW='\033[0;33m' - COLOR_GREEN='\033[0;32m' - COLOR_BLUE='\033[0;34m' - COLOR_CYAN='\033[0;36m' -else - COLOR_RESET='' - COLOR_RED='' - COLOR_YELLOW='' - COLOR_GREEN='' - COLOR_BLUE='' - COLOR_CYAN='' -fi - -# ---------------------------------------------------------------------------- -# Enhanced logging functions -# ---------------------------------------------------------------------------- - -log_debug() { - if [ "${DEBUG:-false}" = "true" ]; then - printf '%b[DEBUG]%b %s\n' "${COLOR_CYAN}" "${COLOR_RESET}" "$*" - fi -} - -log_success() { - printf '%b[SUCCESS]%b %s\n' "${COLOR_GREEN}" "${COLOR_RESET}" "$*" -} - -log_step() { - printf '%b[STEP]%b %s\n' "${COLOR_BLUE}" "${COLOR_RESET}" "$*" -} - -# ---------------------------------------------------------------------------- -# Structured logging -# ---------------------------------------------------------------------------- - -# Log a key-value pair -log_kv() { - local key="$1" - local value="$2" - printf ' %b%s:%b %s\n' "${COLOR_BLUE}" "${key}" "${COLOR_RESET}" "${value}" -} - -# Log a list item -log_item() { - printf ' %b•%b %s\n' "${COLOR_GREEN}" "${COLOR_RESET}" "$*" -} - -# Log a separator line -log_separator() { - printf '%s\n' "=========================================" -} - -# Log a section header -log_section() { - printf '\n%b=== %s ===%b\n' "${COLOR_BLUE}" "$*" "${COLOR_RESET}" -} - diff --git a/scripts/release/package_extension.sh b/scripts/release/package_extension.sh deleted file mode 100755 index cb2e1fa..0000000 --- a/scripts/release/package_extension.sh +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Release -# INGROUP: Extension.Packaging -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/release/package_extension.sh -# VERSION: 01.00.00 -# BRIEF: Package Joomla extension as distributable ZIP -# USAGE: ./scripts/release/package_extension.sh [output_dir] [version] -# ============================================================================ - -set -euo pipefail - -# Load shared library functions (optional) -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -LIB_DIR="${SCRIPT_DIR}/../lib" - -# Configuration -SRC_DIR="${SRC_DIR:-src}" -OUTPUT_DIR="${1:-dist}" -VERSION="${2:-}" -REPO_NAME="${REPO_NAME:-$(basename "$(git rev-parse --show-toplevel)")}" - -json_escape() { - python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1" -} - -log_info() { - echo "[INFO] $*" >&2 -} - -log_error() { - echo "[ERROR] $*" >&2 -} - -# Validate prerequisites -validate_prerequisites() { - if [ ! -d "${SRC_DIR}" ]; then - log_error "Source directory '${SRC_DIR}' not found" - printf '{"status":"fail","error":%s}\n' "$(json_escape "src directory missing")" - exit 1 - fi - - if ! command -v zip >/dev/null 2>&1; then - log_error "zip command not found. Please install zip utility." - printf '{"status":"fail","error":%s}\n' "$(json_escape "zip command not found")" - exit 1 - fi -} - -# Find and validate manifest -find_manifest_file() { - local manifest="" - - # Priority order for finding manifest - if [ -f "${SRC_DIR}/templateDetails.xml" ]; then - manifest="${SRC_DIR}/templateDetails.xml" - elif [ -f "${SRC_DIR}/templates/templateDetails.xml" ]; then - manifest="${SRC_DIR}/templates/templateDetails.xml" - else - # Try finding any Joomla manifest - manifest=$(find "${SRC_DIR}" -maxdepth 3 -type f \( \ - -name 'templateDetails.xml' -o \ - -name 'pkg_*.xml' -o \ - -name 'mod_*.xml' -o \ - -name 'com_*.xml' -o \ - -name 'plg_*.xml' \ - \) | head -n 1) - fi - - if [ -z "${manifest}" ]; then - log_error "No Joomla manifest XML found in ${SRC_DIR}" - printf '{"status":"fail","error":%s}\n' "$(json_escape "manifest not found")" - exit 1 - fi - - echo "${manifest}" -} - -# Extract extension metadata from manifest -get_extension_metadata() { - local manifest="$1" - local ext_type="" - local ext_name="" - local ext_version="" - - # Extract extension type - ext_type=$(grep -Eo 'type="[^"]+"' "${manifest}" | head -n 1 | cut -d '"' -f2 || echo "unknown") - - # Extract extension name - ext_name=$(grep -oP '\K[^<]+' "${manifest}" | head -n 1 || echo "unknown") - - # Extract version - ext_version=$(grep -oP '\K[^<]+' "${manifest}" | head -n 1 || echo "unknown") - - echo "${ext_type}|${ext_name}|${ext_version}" -} - -# Create package -create_package() { - local manifest="$1" - local output_dir="$2" - local version="$3" - - # Get extension metadata - local metadata - metadata=$(get_extension_metadata "${manifest}") - local ext_type=$(echo "${metadata}" | cut -d '|' -f1) - local ext_name=$(echo "${metadata}" | cut -d '|' -f2) - local manifest_version=$(echo "${metadata}" | cut -d '|' -f3) - - # Use provided version or fall back to manifest version - if [ -z "${version}" ]; then - version="${manifest_version}" - fi - - # Create output directory - mkdir -p "${output_dir}" - - # Generate package filename - local timestamp=$(date +%Y%m%d-%H%M%S) - local zip_name="${REPO_NAME}-${version}-${ext_type}.zip" - - # Get absolute path for zip file - local abs_output_dir - if [[ "${output_dir}" = /* ]]; then - abs_output_dir="${output_dir}" - else - abs_output_dir="$(pwd)/${output_dir}" - fi - local zip_path="${abs_output_dir}/${zip_name}" - - log_info "Creating package: ${zip_name}" - log_info "Extension: ${ext_name} (${ext_type})" - log_info "Version: ${version}" - - # Create ZIP archive excluding unnecessary files - (cd "${SRC_DIR}" && zip -r -q -X "${zip_path}" . \ - -x '*.git*' \ - -x '*/.github/*' \ - -x '*.DS_Store' \ - -x '*/__MACOSX/*' \ - -x '*/node_modules/*' \ - -x '*/vendor/*' \ - -x '*/tests/*' \ - -x '*/.phpunit.result.cache' \ - -x '*/codeception.yml' \ - -x '*/composer.json' \ - -x '*/composer.lock' \ - -x '*/package.json' \ - -x '*/package-lock.json') - - # Get file size - local zip_size - if command -v stat >/dev/null 2>&1; then - zip_size=$(stat -f%z "${zip_path}" 2>/dev/null || stat -c%s "${zip_path}" 2>/dev/null || echo "unknown") - else - zip_size="unknown" - fi - - log_info "Package created successfully: ${zip_path}" - log_info "Package size: ${zip_size} bytes" - - # Output JSON result - printf '{"status":"ok","package":%s,"type":%s,"version":%s,"size":%s,"manifest":%s}\n' \ - "$(json_escape "${zip_path}")" \ - "$(json_escape "${ext_type}")" \ - "$(json_escape "${version}")" \ - "${zip_size}" \ - "$(json_escape "${manifest}")" -} - -# Main execution -main() { - log_info "Starting Joomla extension packaging" - - validate_prerequisites - - local manifest - manifest=$(find_manifest_file) - log_info "Using manifest: ${manifest}" - - create_package "${manifest}" "${OUTPUT_DIR}" "${VERSION}" - - log_info "Packaging completed successfully" -} - -# Run main function -main "$@" diff --git a/scripts/release/update_changelog.sh b/scripts/release/update_changelog.sh deleted file mode 100755 index d6cd950..0000000 --- a/scripts/release/update_changelog.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env bash -# ----------------------------------------------------------------------------- - -# Copyright (C) 2025 Moko Consulting -# -# 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 (./LICENSE). -# ----------------------------------------------------------------------------- -# FILE INFORMATION -# DEFGROUP: MokoStandards -# INGROUP: Generic.Script -# REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /scripts/update_changelog.sh -# VERSION: 01.00.00 -# BRIEF: Insert a versioned CHANGELOG.md entry immediately after the main Changelog heading -# Purpose: -# - Apply the MokoWaaS-Brand CHANGELOG template entry for a given version. -# - Insert a new header at the top of CHANGELOG.md, immediately after "# Changelog". -# - Avoid duplicates if an entry for the version already exists. -# - Preserve the rest of the file verbatim. -# -# Usage: -# ./scripts/update_changelog.sh -# -# Example: -# ./scripts/update_changelog.sh 01.05.00 -# ============================================================================= - -set -euo pipefail - -CHANGELOG_FILE="CHANGELOG.md" - -die() { - echo "ERROR: $*" 1>&2 - exit 1 -} - -info() { - echo "INFO: $*" -} - -require_cmd() { - command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" -} - -validate/version() { - local v="$1" - [[ "$v" =~ ^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$ ]] || die "Invalid version '$v'. Expected NN.NN.NN (example 03.01.00)." -} - -main() { - require_cmd awk - require_cmd grep - require_cmd mktemp - require_cmd date - - [[ $# -eq 1 ]] || die "Usage: $0 " - local version="$1" - validate/version "$version" - - [[ -f "$CHANGELOG_FILE" ]] || die "Missing $CHANGELOG_FILE in repo root." - - if ! grep -qE '^# Changelog[[:space:]]*$' "$CHANGELOG_FILE"; then - die "$CHANGELOG_FILE must contain a top level heading exactly: # Changelog" - fi - - if grep -qE "^## \[$version\][[:space:]]" "$CHANGELOG_FILE"; then - info "CHANGELOG.md already contains an entry for version $version. No action taken." - exit 0 - fi - - local stamp - stamp="$(date '+%Y-%m-%d')" - - local tmp - tmp="$(mktemp)" - trap 'rm -f "$tmp"' EXIT - - awk -v v="$version" -v d="$stamp" ' - BEGIN { inserted=0 } - { - print $0 - if (inserted==0 && $0 ~ /^# Changelog[[:space:]]*$/) { - print "" - print "## [" v "] " d - print "- Version bump." - print "" - inserted=1 - } - } - END { - if (inserted==0) { - exit 3 - } - } - ' "$CHANGELOG_FILE" > "$tmp" || { - rc=$? - if [[ $rc -eq 3 ]]; then - die "Insertion point not found. Expected: # Changelog" - fi - die "Failed to update $CHANGELOG_FILE (awk exit code $rc)." - } - - mv "$tmp" "$CHANGELOG_FILE" - trap - EXIT - - info "Inserted CHANGELOG.md entry for version $version on $stamp." -} - -main "$@" diff --git a/scripts/release/update_dates.sh b/scripts/release/update_dates.sh deleted file mode 100755 index 3e8ed79..0000000 --- a/scripts/release/update_dates.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (C) 2025 Moko Consulting -# -# 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. If not, see . -# -# FILE INFORMATION -# DEFGROUP: Release.Automation -# INGROUP: Date.Normalization -# REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /scripts/release/update_dates.sh -# VERSION: 01.00.00 -# BRIEF: Normalize release dates across manifests and CHANGELOG using a single authoritative UTC date. -# NOTE: Repo-controlled script only. CI-fatal on malformed inputs. Outputs a JSON report to stdout. - -set -euo pipefail - -TODAY_UTC="${1:-}" -VERSION="${2:-}" - -usage() { - echo "ERROR: Usage: update_dates.sh " >&2 -} - -if [ -z "${TODAY_UTC}" ] || [ -z "${VERSION}" ]; then - usage - exit 1 -fi - -# Validate date format strictly -if ! echo "${TODAY_UTC}" | grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then - echo "ERROR: Invalid date format. Expected YYYY-MM-DD, got '${TODAY_UTC}'" >&2 - exit 1 -fi - -# Validate version format strictly -if ! echo "${VERSION}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "ERROR: Invalid version format. Expected X.Y.Z, got '${VERSION}'" >&2 - exit 1 -fi - -# Cross-platform sed in-place helper (GNU and BSD) -# - Ubuntu runners use GNU sed, but this keeps local execution deterministic. -sed_inplace() { - local expr="$1" - local file="$2" - - if sed --version >/dev/null 2>&1; then - sed -i -E "${expr}" "${file}" - else - sed -i '' -E "${expr}" "${file}" - fi -} - -echo "Normalizing dates to ${TODAY_UTC} for version ${VERSION}" - -# Update CHANGELOG.md heading date -if [ ! -f CHANGELOG.md ]; then - echo "ERROR: CHANGELOG.md not found" >&2 - exit 1 -fi - -if ! grep -Eq "^## \[${VERSION}\]" CHANGELOG.md; then - echo "ERROR: CHANGELOG.md does not contain heading for version [${VERSION}]" >&2 - exit 1 -fi - -# Use a delimiter that will not collide with the pattern (the heading starts with "##") -sed_inplace "s|^(## \[${VERSION}\]) .*|\1 ${TODAY_UTC}|" CHANGELOG.md - -# Update XML manifest dates -XML_SCANNED=0 -XML_TOUCHED=0 - -while IFS= read -r -d '' FILE; do - XML_SCANNED=$((XML_SCANNED + 1)) - - BEFORE_HASH="" - AFTER_HASH="" - - # Best-effort content hash for change detection without external deps. - if command -v sha256sum >/dev/null 2>&1; then - BEFORE_HASH="$(sha256sum "${FILE}" | awk '{print $1}')" - fi - - # Use # delimiter because XML does not include # in these tags. - sed -i "s#[^<]*#${TODAY_UTC}#g" "${FILE}" || true - sed -i "s#[^<]*#${TODAY_UTC}#g" "${FILE}" || true - sed -i "s#[^<]*#${TODAY_UTC}#g" "${FILE}" || true - - if [ -n "${BEFORE_HASH}" ]; then - AFTER_HASH="$(sha256sum "${FILE}" | awk '{print $1}')" - if [ "${BEFORE_HASH}" != "${AFTER_HASH}" ]; then - XML_TOUCHED=$((XML_TOUCHED + 1)) - fi - fi - -done < <( - find . -type f -name "*.xml" \ - -not -path "./.git/*" \ - -not -path "./.github/*" \ - -not -path "./dist/*" \ - -not -path "./node_modules/*" \ - -print0 -) - -# JSON report to stdout (workflow can capture or include in summary) -printf '{"today_utc":"%s","version":"%s","changelog":"%s","xml_scanned":%s,"xml_touched":%s} -' \ - "${TODAY_UTC}" \ - "${VERSION}" \ - "CHANGELOG.md" \ - "${XML_SCANNED}" \ - "${XML_TOUCHED}" - -echo "Date normalization complete." diff --git a/scripts/run/check_version.sh b/scripts/run/check_version.sh deleted file mode 100755 index d36b114..0000000 --- a/scripts/run/check_version.sh +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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: Version.Management -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/run/check_version.sh -# VERSION: 01.00.00 -# BRIEF: Check if a version can be created in a specific branch prefix -# NOTE: Validates against version hierarchy rules before creation -# ============================================================================ - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# ---------------------------------------------------------------------------- -# Usage and validation -# ---------------------------------------------------------------------------- - -usage() { -cat <<-USAGE -Usage: $0 - -Check if a version can be created in the specified branch prefix. - -Arguments: - BRANCH_PREFIX One of: dev/, rc/, version/ - VERSION Version in format NN.NN.NN (e.g., 03.01.00) - -Examples: - $0 dev/ 03.05.00 - $0 rc/ 03.01.00 - $0 version/ 02.00.00 - -Exit codes: - 0 - Version can be created (no conflicts) - 1 - Version cannot be created (conflicts found) - 2 - Invalid arguments or usage - -Version Hierarchy Rules: - - version/X.Y.Z (stable) - always allowed, highest priority - - rc/X.Y.Z (RC) - blocked if version/X.Y.Z exists - - dev/X.Y.Z (dev) - blocked if version/X.Y.Z or rc/X.Y.Z exists - -USAGE -exit 2 -} - -validate_prefix() { - local prefix="$1" - case "$prefix" in - "dev/"|"rc/"|"version/") - return 0 - ;; - *) - die "Invalid branch prefix: $prefix (must be dev/, rc/, or version/)" - ;; - esac -} - -validate_version() { - local v="$1" - if [[ ! "$v" =~ ^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$ ]]; then - die "Invalid version format: $v (expected NN.NN.NN)" - fi -} - -# ---------------------------------------------------------------------------- -# Main logic -# ---------------------------------------------------------------------------- - -check_version_can_be_created() { - local prefix="$1" - local version="$2" - - log_info "Checking if ${prefix}${version} can be created..." - log_info "" - - # Check if the exact branch already exists - if git ls-remote --exit-code --heads origin "${prefix}${version}" >/dev/null 2>&1; then - log_error "✗ Branch already exists: ${prefix}${version}" - return 1 - fi - - local conflicts=0 - - case "$prefix" in - "version/") - log_info "Creating stable version - no hierarchy checks needed" - log_info "✓ version/${version} can be created" - ;; - "rc/") - log_info "Checking if version exists in stable..." - if git ls-remote --exit-code --heads origin "version/${version}" >/dev/null 2>&1; then - log_error "✗ CONFLICT: Version ${version} already exists in stable (version/${version})" - log_error " Cannot create RC for a version that exists in stable" - conflicts=$((conflicts + 1)) - else - log_info "✓ No conflict with stable versions" - log_info "✓ rc/${version} can be created" - fi - ;; - "dev/") - log_info "Checking if version exists in stable..." - if git ls-remote --exit-code --heads origin "version/${version}" >/dev/null 2>&1; then - log_error "✗ CONFLICT: Version ${version} already exists in stable (version/${version})" - log_error " Cannot create dev for a version that exists in stable" - conflicts=$((conflicts + 1)) - else - log_info "✓ No conflict with stable versions" - fi - - log_info "Checking if version exists in RC..." - if git ls-remote --exit-code --heads origin "rc/${version}" >/dev/null 2>&1; then - log_error "✗ CONFLICT: Version ${version} already exists in RC (rc/${version})" - log_error " Cannot create dev for a version that exists in RC" - conflicts=$((conflicts + 1)) - else - log_info "✓ No conflict with RC versions" - fi - - if [ $conflicts -eq 0 ]; then - log_info "✓ dev/${version} can be created" - fi - ;; - esac - - log_info "" - - if [ $conflicts -gt 0 ]; then - log_error "Version ${prefix}${version} cannot be created ($conflicts conflict(s) found)" - return 1 - fi - - log_info "Version ${prefix}${version} can be created safely" - return 0 -} - -# ---------------------------------------------------------------------------- -# Main -# ---------------------------------------------------------------------------- - -# Parse arguments -if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then - usage -fi - -[ $# -eq 2 ] || usage - -BRANCH_PREFIX="$1" -VERSION="$2" - -validate_prefix "$BRANCH_PREFIX" -validate_version "$VERSION" - -log_info "Version Creation Check" -log_info "======================" -log_info "" - -check_version_can_be_created "$BRANCH_PREFIX" "$VERSION" -exit_code=$? - -log_info "" -log_info "======================" - -exit $exit_code diff --git a/scripts/run/list_versions.sh b/scripts/run/list_versions.sh deleted file mode 100755 index ebf22d6..0000000 --- a/scripts/run/list_versions.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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: Version.Management -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/run/list_versions.sh -# VERSION: 01.00.00 -# BRIEF: List all version branches organized by prefix -# NOTE: Displays dev/, rc/, and version/ branches in a structured format -# ============================================================================ - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# ---------------------------------------------------------------------------- -# Functions -# ---------------------------------------------------------------------------- - -list_version_branches() { - log_info "Fetching version branches from remote..." - - # Get all branches with version-like names - local branches - branches=$(git ls-remote --heads origin 2>/dev/null | awk '{print $2}' | sed 's|refs/heads/||' || echo "") - - if [ -z "$branches" ]; then - log_warn "No remote branches found or unable to fetch branches" - return 0 - fi - - # Categorize versions - local dev_versions=() - local rc_versions=() - local stable_versions=() - - while IFS= read -r branch; do - if [[ "$branch" =~ ^dev/([0-9]{2}\.[0-9]{2}\.[0-9]{2})$ ]]; then - dev_versions+=("${BASH_REMATCH[1]}") - elif [[ "$branch" =~ ^rc/([0-9]{2}\.[0-9]{2}\.[0-9]{2})$ ]]; then - rc_versions+=("${BASH_REMATCH[1]}") - elif [[ "$branch" =~ ^version/([0-9]{2}\.[0-9]{2}\.[0-9]{2})$ ]]; then - stable_versions+=("${BASH_REMATCH[1]}") - fi - done <<< "$branches" - - # Sort versions - IFS=$'\n' dev_versions=($(sort -V <<< "${dev_versions[*]}" 2>/dev/null || echo "${dev_versions[@]}")) - IFS=$'\n' rc_versions=($(sort -V <<< "${rc_versions[*]}" 2>/dev/null || echo "${rc_versions[@]}")) - IFS=$'\n' stable_versions=($(sort -V <<< "${stable_versions[*]}" 2>/dev/null || echo "${stable_versions[@]}")) - unset IFS - - # Display results - echo "" - echo "========================================" - echo "Version Branches Summary" - echo "========================================" - echo "" - - echo "📦 Stable Versions (version/)" - echo "----------------------------------------" - if [ ${#stable_versions[@]} -eq 0 ]; then - echo " (none)" - else - for version in "${stable_versions[@]}"; do - echo " ✓ version/$version" - done - fi - echo "" - - echo "🔧 Release Candidates (rc/)" - echo "----------------------------------------" - if [ ${#rc_versions[@]} -eq 0 ]; then - echo " (none)" - else - for version in "${rc_versions[@]}"; do - echo " ➜ rc/$version" - done - fi - echo "" - - echo "🚧 Development Versions (dev/)" - echo "----------------------------------------" - if [ ${#dev_versions[@]} -eq 0 ]; then - echo " (none)" - else - for version in "${dev_versions[@]}"; do - echo " ⚡ dev/$version" - done - fi - echo "" - - echo "========================================" - echo "Total: ${#stable_versions[@]} stable, ${#rc_versions[@]} RC, ${#dev_versions[@]} dev" - echo "========================================" -} - -# ---------------------------------------------------------------------------- -# Main -# ---------------------------------------------------------------------------- - -list_version_branches diff --git a/scripts/run/migrate_unreleased.sh b/scripts/run/migrate_unreleased.sh deleted file mode 100755 index 4ab0ff9..0000000 --- a/scripts/run/migrate_unreleased.sh +++ /dev/null @@ -1,292 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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: Repository.Release -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/run/migrate_unreleased.sh -# VERSION: 01.00.00 -# BRIEF: Migrate unreleased changelog entries to a versioned section -# NOTE: Moves content from [Unreleased] section to a specified version heading -# ============================================================================ - -set -euo pipefail - -# ---------------------------------------------------------------------------- -# Usage -# ---------------------------------------------------------------------------- - -usage() { -cat <<-USAGE -Usage: $0 [OPTIONS] - -Migrate unreleased changelog entries to a versioned section. - -Arguments: - VERSION Version number in format NN.NN.NN (e.g., 03.05.00) - -Options: - -h, --help Show this help message - -d, --date Date to use for version entry (default: today, format: YYYY-MM-DD) - -n, --dry-run Show what would be done without making changes - -k, --keep Keep the [Unreleased] section after migration (default: empty it) - -Examples: - $0 03.05.00 # Migrate unreleased to version 03.05.00 - $0 03.05.00 --date 2026-01-04 # Use specific date - $0 03.05.00 --dry-run # Preview changes without applying - $0 03.05.00 --keep # Keep unreleased section after migration - -USAGE -exit 0 -} - -# ---------------------------------------------------------------------------- -# Argument parsing -# ---------------------------------------------------------------------------- - -VERSION="" -DATE="" -DRY_RUN=false -KEEP_UNRELEASED=false - -while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) - usage - ;; - -d|--date) - DATE="$2" - shift 2 - ;; - -n|--dry-run) - DRY_RUN=true - shift - ;; - -k|--keep) - KEEP_UNRELEASED=true - shift - ;; - *) - if [[ -z "$VERSION" ]]; then - VERSION="$1" - shift - else - echo "ERROR: Unknown argument: $1" >&2 - usage - fi - ;; - esac -done - -# ---------------------------------------------------------------------------- -# Validation -# ---------------------------------------------------------------------------- - -if [[ -z "$VERSION" ]]; then - echo "ERROR: VERSION is required" >&2 - usage -fi - -if ! [[ "$VERSION" =~ ^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$ ]]; then - echo "ERROR: Invalid version format: $VERSION" >&2 - echo "Expected format: NN.NN.NN (e.g., 03.05.00)" >&2 - exit 1 -fi - -if [[ -z "$DATE" ]]; then - DATE=$(date '+%Y-%m-%d') -fi - -if ! [[ "$DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then - echo "ERROR: Invalid date format: $DATE" >&2 - echo "Expected format: YYYY-MM-DD" >&2 - exit 1 -fi - -# ---------------------------------------------------------------------------- -# Source common utilities -# ---------------------------------------------------------------------------- - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# ---------------------------------------------------------------------------- -# Main logic -# ---------------------------------------------------------------------------- - -CHANGELOG_FILE="CHANGELOG.md" - -if [[ ! -f "$CHANGELOG_FILE" ]]; then - log_error "CHANGELOG.md not found in repository root" - exit 1 -fi - -log_info "Migrating unreleased changelog entries to version $VERSION" -log_info "Date: $DATE" -log_info "Dry run: $DRY_RUN" -log_info "Keep unreleased section: $KEEP_UNRELEASED" - -# Use Python to process the changelog -python3 - < bool: - return line.lstrip().startswith("## ") - -def norm(line: str) -> str: - return line.strip().lower() - -def find_idx(predicate): - for i, ln in enumerate(lines): - if predicate(ln): - return i - return None - -unreleased_idx = find_idx(lambda ln: norm(ln) == "## [unreleased]") -version_idx = find_idx(lambda ln: ln.lstrip().startswith(f"## [{version}]")) - -def version_header() -> list: - return ["\n", f"## [{version}] {stamp}\n", "\n"] - -if unreleased_idx is None: - print(f"INFO: No [Unreleased] section found in {changelog_path}") - if version_idx is None: - print(f"INFO: Version section [{version}] does not exist") - print(f"INFO: Creating new version section with placeholder content") - if not dry_run: - # Find insertion point after main heading - insert_at = 0 - for i, ln in enumerate(lines): - if ln.lstrip().startswith("# "): - insert_at = i + 1 - while insert_at < len(lines) and lines[insert_at].strip() == "": - insert_at += 1 - break - entry = version_header() + ["- No changes recorded.\n", "\n"] - lines[insert_at:insert_at] = entry - changelog_path.write_text("".join(lines), encoding="utf-8") - print(f"SUCCESS: Created version section [{version}]") - else: - print(f"DRY-RUN: Would create version section [{version}]") - else: - print(f"INFO: Version section [{version}] already exists") - sys.exit(0) - -# Extract unreleased content -u_start = unreleased_idx + 1 -u_end = len(lines) -for j in range(u_start, len(lines)): - if is_h2(lines[j]): - u_end = j - break - -unreleased_body = "".join(lines[u_start:u_end]).strip() - -if not unreleased_body: - print(f"INFO: [Unreleased] section is empty, nothing to migrate") - sys.exit(0) - -print(f"INFO: Found unreleased content ({len(unreleased_body)} chars)") - -# Create or find version section -if version_idx is None: - print(f"INFO: Creating version section [{version}]") - if not dry_run: - lines[u_end:u_end] = version_header() - else: - print(f"DRY-RUN: Would create version section [{version}]") - -version_idx = find_idx(lambda ln: ln.lstrip().startswith(f"## [{version}]")) -if version_idx is None and not dry_run: - print("ERROR: Failed to locate version header after insertion", file=sys.stderr) - sys.exit(1) - -# Move unreleased content to version section -if unreleased_body: - if not dry_run: - insert_at = version_idx + 1 - while insert_at < len(lines) and lines[insert_at].strip() == "": - insert_at += 1 - - moved = ["\n"] + [ln + "\n" for ln in unreleased_body.split("\n") if ln != ""] + ["\n"] - lines[insert_at:insert_at] = moved - print(f"INFO: Moved {len([ln for ln in unreleased_body.split('\n') if ln])} lines to [{version}]") - else: - line_count = len([ln for ln in unreleased_body.split('\n') if ln]) - print(f"DRY-RUN: Would move {line_count} lines to [{version}]") - print(f"DRY-RUN: Content preview:") - for line in unreleased_body.split('\n')[:5]: - if line: - print(f" {line}") - -# Handle unreleased section -if not keep_unreleased: - unreleased_idx = find_idx(lambda ln: norm(ln) == "## [unreleased]") - if unreleased_idx is not None: - if not dry_run: - u_start = unreleased_idx + 1 - u_end = len(lines) - for j in range(u_start, len(lines)): - if is_h2(lines[j]): - u_end = j - break - lines[u_start:u_end] = ["\n"] - print(f"INFO: Emptied [Unreleased] section") - else: - print(f"DRY-RUN: Would empty [Unreleased] section") -else: - print(f"INFO: Keeping [Unreleased] section as requested") - -if not dry_run: - changelog_path.write_text("".join(lines), encoding="utf-8") - print(f"SUCCESS: Migrated unreleased content to [{version}]") -else: - print(f"DRY-RUN: Changes not applied (use without --dry-run to apply)") - -PY - -if [[ $? -eq 0 ]]; then - if [[ "$DRY_RUN" == "false" ]]; then - log_info "✓ Migration completed successfully" - log_info "✓ Changelog updated: $CHANGELOG_FILE" - else - log_info "✓ Dry run completed" - fi -else - log_error "Migration failed" - exit 1 -fi diff --git a/scripts/run/script_health.sh b/scripts/run/script_health.sh deleted file mode 100755 index ed0f313..0000000 --- a/scripts/run/script_health.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Script.Health -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/run/script_health.sh -# VERSION: 01.00.00 -# BRIEF: Validate scripts follow enterprise standards -# NOTE: Checks for copyright headers, error handling, and documentation -# ============================================================================ - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" -. "${SCRIPT_DIR}/lib/logging.sh" - -# ---------------------------------------------------------------------------- -# Usage -# ---------------------------------------------------------------------------- - -usage() { -cat <<-USAGE -Usage: $0 [OPTIONS] - -Validate that all scripts follow enterprise standards. - -Options: - -v, --verbose Show detailed output - -h, --help Show this help message - -Checks performed: - - Copyright headers present - - SPDX license identifier present - - FILE INFORMATION section present - - set -euo pipefail present - - Executable permissions set - -Examples: - $0 # Run all health checks - $0 -v # Verbose output - $0 --help # Show usage - -Exit codes: - 0 - All checks passed - 1 - One or more checks failed - 2 - Invalid arguments - -USAGE -exit 0 -} - -# ---------------------------------------------------------------------------- -# Configuration -# ---------------------------------------------------------------------------- - -VERBOSE="${1:-}" - -case "${VERBOSE}" in - -h|--help) - usage - ;; - -v|--verbose) - VERBOSE="true" - ;; - "") - VERBOSE="false" - ;; - *) - log_error "Invalid argument: ${VERBOSE}" - echo "" - usage - exit 2 - ;; -esac - -log_info "Running script health checks" -log_info "Start time: $(log_timestamp)" - -START_TIME=$(date +%s) - -# ---------------------------------------------------------------------------- -# Health checks -# ---------------------------------------------------------------------------- - -total_scripts=0 -missing_copyright=0 -missing_spdx=0 -missing_fileinfo=0 -missing_error_handling=0 -not_executable=0 - -check_script() { - local script="$1" - local errors=0 - - total_scripts=$((total_scripts + 1)) - - # Check for copyright - if ! grep -q "Copyright (C)" "$script"; then - missing_copyright=$((missing_copyright + 1)) - errors=$((errors + 1)) - [ "${VERBOSE}" = "true" ] && log_warn "Missing copyright: $script" - fi - - # Check for SPDX - if ! grep -q "SPDX-License-Identifier" "$script"; then - missing_spdx=$((missing_spdx + 1)) - errors=$((errors + 1)) - [ "${VERBOSE}" = "true" ] && log_warn "Missing SPDX: $script" - fi - - # Check for FILE INFORMATION - if ! grep -q "FILE INFORMATION" "$script"; then - missing_fileinfo=$((missing_fileinfo + 1)) - errors=$((errors + 1)) - [ "${VERBOSE}" = "true" ] && log_warn "Missing FILE INFORMATION: $script" - fi - - # Check for error handling (bash scripts only) - if [[ "$script" == *.sh ]]; then - if ! grep -q "set -e" "$script"; then - missing_error_handling=$((missing_error_handling + 1)) - errors=$((errors + 1)) - [ "${VERBOSE}" = "true" ] && log_warn "Missing error handling: $script" - fi - fi - - # Check executable permission - if [ ! -x "$script" ]; then - not_executable=$((not_executable + 1)) - errors=$((errors + 1)) - [ "${VERBOSE}" = "true" ] && log_warn "Not executable: $script" - fi - - return $errors -} - -# Find all shell scripts -log_info "Scanning scripts directory..." - -while IFS= read -r -d '' script; do - check_script "$script" || true -done < <(find "${SCRIPT_DIR}" -type f -name "*.sh" -print0) - -# ---------------------------------------------------------------------------- -# Summary -# ---------------------------------------------------------------------------- - -END_TIME=$(date +%s) - -log_separator -log_info "Script Health Summary" -log_separator -log_kv "Total scripts checked" "${total_scripts}" -log_kv "Missing copyright" "${missing_copyright}" -log_kv "Missing SPDX identifier" "${missing_spdx}" -log_kv "Missing FILE INFORMATION" "${missing_fileinfo}" -log_kv "Missing error handling" "${missing_error_handling}" -log_kv "Not executable" "${not_executable}" -log_separator -log_info "End time: $(log_timestamp)" -log_info "Duration: $(log_duration "$START_TIME" "$END_TIME")" - -total_issues=$((missing_copyright + missing_spdx + missing_fileinfo + missing_error_handling + not_executable)) - -if [ "$total_issues" -eq 0 ]; then - log_success "SUCCESS: All scripts follow enterprise standards" - exit 0 -else - log_error "FAILED: Found ${total_issues} standard violation(s)" - log_info "Run with -v flag for details on which scripts need updates" - exit 1 -fi diff --git a/scripts/run/smoke_test.sh b/scripts/run/smoke_test.sh deleted file mode 100755 index 66bec88..0000000 --- a/scripts/run/smoke_test.sh +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Test -# INGROUP: Repository.Validation -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/run/smoke_test.sh -# VERSION: 01.00.00 -# BRIEF: Basic smoke tests to verify repository structure and manifest validity -# NOTE: Quick validation checks for essential repository components -# ============================================================================ - -set -euo pipefail - -# ---------------------------------------------------------------------------- -# Usage -# ---------------------------------------------------------------------------- - -usage() { -cat <<-USAGE -Usage: $0 [OPTIONS] - -Run basic smoke tests to verify repository structure and manifest validity. - -Options: - -h, --help Show this help message - -Examples: - $0 # Run all smoke tests - $0 --help # Show usage information - -USAGE -exit 0 -} - -# Parse arguments -if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then - usage -fi - -# Source common utilities -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# Check dependencies -check_dependencies python3 - -START_TIME=$(date +%s) - -log_info "Running smoke tests for Moko-Cassiopeia repository" -log_info "Start time: $(log_timestamp)" - -# ---------------------------------------------------------------------------- -# Test: Repository structure -# ---------------------------------------------------------------------------- - -log_info "Checking repository structure..." - -assert_dir_exists "src" -assert_file_exists "README.md" -assert_file_exists "CHANGELOG.md" -assert_file_exists "LICENSE" -assert_file_exists "CONTRIBUTING.md" - -log_info "✓ Repository structure valid" - -# ---------------------------------------------------------------------------- -# Test: Manifest validation -# ---------------------------------------------------------------------------- - -log_info "Checking Joomla manifest..." - -. "${SCRIPT_DIR}/lib/joomla_manifest.sh" - -MANIFEST="$(find_manifest src)" -log_info "Found manifest: ${MANIFEST}" - -VERSION="$(get_manifest_version "${MANIFEST}")" -NAME="$(get_manifest_name "${MANIFEST}")" -TYPE="$(get_manifest_type "${MANIFEST}")" - -log_info "Extension: ${NAME} (${TYPE}) v${VERSION}" - -# Verify manifest is well-formed XML -require_cmd python3 -python3 - "${MANIFEST}" <<'PY' -import sys -import xml.etree.ElementTree as ET - -manifest_path = sys.argv[1] - -try: - tree = ET.parse(manifest_path) - root = tree.getroot() - if root.tag != "extension": - print(f"ERROR: Root element must be , got <{root.tag}>") - sys.exit(1) - print("✓ Manifest XML is well-formed") -except Exception as e: - print(f"ERROR: Failed to parse manifest: {e}") - sys.exit(1) -PY - -log_info "✓ Manifest validation passed" - -# ---------------------------------------------------------------------------- -# Test: Version alignment -# ---------------------------------------------------------------------------- - -log_info "Checking version alignment..." - -if [ ! -f "CHANGELOG.md" ]; then -log_warn "CHANGELOG.md not found, skipping version alignment check" -else -if grep -q "## \[${VERSION}\]" CHANGELOG.md; then -log_info "✓ Version ${VERSION} found in CHANGELOG.md" -else -log_warn "Version ${VERSION} not found in CHANGELOG.md" -fi -fi - -# ---------------------------------------------------------------------------- -# Test: Critical files syntax -# ---------------------------------------------------------------------------- - -log_info "Checking PHP syntax..." - -if command -v php >/dev/null 2>&1; then -php_errors=0 -failed_files=() -while IFS= read -r -d '' f; do -if ! php_output=$(php -l "$f" 2>&1); then -log_error "PHP syntax error in: $f" -echo " Error details:" >&2 -echo "$php_output" | sed 's/^/ /' >&2 -echo "" >&2 -php_errors=$((php_errors + 1)) -failed_files+=("$f") -fi -done < <(find src -type f -name '*.php' -print0 2>/dev/null) - -if [ "${php_errors}" -eq 0 ]; then -log_info "✓ PHP syntax validation passed" -else -echo "Summary of PHP syntax errors:" >&2 -echo " Total errors: ${php_errors}" >&2 -echo " Failed files:" >&2 -for f in "${failed_files[@]}"; do -echo " - $f" >&2 -done -echo "" >&2 -echo "To fix: Run 'php -l ' on each failed file for detailed error messages." >&2 -die "Found ${php_errors} PHP syntax errors" -fi -else -log_warn "PHP not available, skipping PHP syntax check" -fi - -# ---------------------------------------------------------------------------- -# Summary -# ---------------------------------------------------------------------------- - -log_info "=========================================" -log_info "Smoke tests completed successfully" -log_info "Extension: ${NAME}" -log_info "Version: ${VERSION}" -log_info "Type: ${TYPE}" -log_info "End time: $(log_timestamp)" -END_TIME=$(date +%s) -log_info "Duration: $(log_duration "$START_TIME" "$END_TIME")" -log_info "=========================================" diff --git a/scripts/run/validate_all.sh b/scripts/run/validate_all.sh deleted file mode 100755 index 7149176..0000000 --- a/scripts/run/validate_all.sh +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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: Repository.Validation -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/run/validate_all.sh -# VERSION: 01.00.00 -# BRIEF: Run all validation scripts and report results -# NOTE: Helpful for developers to run all checks before committing -# ============================================================================ - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" -. "${SCRIPT_DIR}/lib/logging.sh" - -# ---------------------------------------------------------------------------- -# Usage -# ---------------------------------------------------------------------------- - -usage() { -cat <<-USAGE -Usage: $0 [OPTIONS] - -Run all validation scripts and report results. - -Options: - -v, --verbose Show detailed output from validation scripts - -h, --help Show this help message - -Examples: - $0 # Run all validations in quiet mode - $0 -v # Run with verbose output - $0 --help # Show usage information - -Exit codes: - 0 - All required checks passed - 1 - One or more required checks failed - 2 - Invalid arguments - -USAGE -exit 0 -} - -# ---------------------------------------------------------------------------- -# Configuration -# ---------------------------------------------------------------------------- - -VERBOSE="${1:-}" - -# Parse arguments -case "${VERBOSE}" in - -h|--help) - usage - ;; - -v|--verbose) - VERBOSE="true" - ;; - "") - VERBOSE="false" - ;; - *) - log_error "Invalid argument: ${VERBOSE}" - echo "" - usage - exit 2 - ;; -esac - -# Check dependencies -check_dependencies python3 - -START_TIME=$(date +%s) - -log_info "Start time: $(log_timestamp)" - -REQUIRED_CHECKS=( - "manifest" - "xml_wellformed" -) - -OPTIONAL_CHECKS=( - "changelog" - "language_structure" - "license_headers" - "no_secrets" - "paths" - "php_syntax" - "tabs" - "version_alignment" -) - -# ---------------------------------------------------------------------------- -# Run validations -# ---------------------------------------------------------------------------- - -log_section "Repository Validation Suite" -log_info "Running all validation checks..." -log_separator - -required_passed=0 -required_failed=0 -optional_passed=0 -optional_failed=0 - -# Required checks -log_section "Required Checks" -for check in "${REQUIRED_CHECKS[@]}"; do - script="${SCRIPT_DIR}/validate/${check}.sh" - if [ ! -f "${script}" ]; then - log_error "Script not found: ${script}" - required_failed=$((required_failed + 1)) - continue - fi - - log_step "Running: ${check}" - # Always capture output for better error reporting - output="" - if output=$("${script}" 2>&1); then - log_success "✓ ${check}" - required_passed=$((required_passed + 1)) - [ "${VERBOSE}" = "true" ] && echo "$output" - else - log_error "✗ ${check} (FAILED)" - required_failed=$((required_failed + 1)) - # Show error output for required checks regardless of verbose flag - echo "" >&2 - echo "Error output:" >&2 - echo "$output" >&2 - echo "" >&2 - fi -done - -echo "" - -# Optional checks -log_section "Optional Checks" -for check in "${OPTIONAL_CHECKS[@]}"; do - script="${SCRIPT_DIR}/validate/${check}.sh" - if [ ! -f "${script}" ]; then - log_warn "Script not found: ${script}" - continue - fi - - log_step "Running: ${check}" - # Capture output for better error reporting - output="" - if output=$("${script}" 2>&1); then - log_success "✓ ${check}" - optional_passed=$((optional_passed + 1)) - [ "${VERBOSE}" = "true" ] && echo "$output" - else - log_warn "✗ ${check} (warnings/issues found)" - optional_failed=$((optional_failed + 1)) - # Show brief error summary for optional checks, full output in verbose - if [ "${VERBOSE}" = "true" ]; then - echo "" >&2 - echo "Error output:" >&2 - echo "$output" >&2 - echo "" >&2 - else - # Show first few lines of error - echo "" >&2 - echo "Error summary (run with -v for full details):" >&2 - echo "$output" | head -5 >&2 - echo "" >&2 - fi - fi -done - -# ---------------------------------------------------------------------------- -# Summary -# ---------------------------------------------------------------------------- - -echo "" -log_separator -log_section "Validation Summary" -log_separator - -log_kv "Required checks passed" "${required_passed}/${#REQUIRED_CHECKS[@]}" -log_kv "Required checks failed" "${required_failed}" -log_kv "Optional checks passed" "${optional_passed}/${#OPTIONAL_CHECKS[@]}" -log_kv "Optional checks with issues" "${optional_failed}" - -log_separator - -log_info "End time: $(log_timestamp)" -END_TIME=$(date +%s) -log_info "Duration: $(log_duration "$START_TIME" "$END_TIME")" - -if [ "${required_failed}" -gt 0 ]; then - log_error "FAILED: ${required_failed} required check(s) failed" - exit 1 -else - log_success "SUCCESS: All required checks passed" - if [ "${optional_failed}" -gt 0 ]; then - log_warn "Note: ${optional_failed} optional check(s) found issues" - fi - exit 0 -fi diff --git a/scripts/validate/changelog.sh b/scripts/validate/changelog.sh deleted file mode 100755 index f9fc6ef..0000000 --- a/scripts/validate/changelog.sh +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Documentation -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/changelog.sh -# VERSION: 01.00.00 -# BRIEF: Validates CHANGELOG.md structure and version entries -# NOTE: Ensures changelog compliance with Keep a Changelog standard -# ============================================================================ - -set -euo pipefail - -json_escape() { - python3 - <<'PY' "$1" -import json,sys -print(json.dumps(sys.argv[1])) -PY -} - -fail() { - local msg="$1" - local extra="${2:-}" - if [ -n "${extra}" ]; then - printf '{"status":"fail","error":%s,%s}\n' "$(json_escape "${msg}")" "${extra}" - else - printf '{"status":"fail","error":%s}\n' "$(json_escape "${msg}")" - fi - exit 1 -} - -ok() { - local extra="${1:-}" - if [ -n "${extra}" ]; then - printf '{"status":"ok",%s}\n' "${extra}" - else - printf '{"status":"ok"}\n' - fi -} - -# Version resolution order: -# 1) explicit env: RELEASE_VERSION or VERSION -# 2) branch name (GITHUB_REF_NAME): rc/x.y.z or version/x.y.z or dev/x.y.z -# 3) tag name (GITHUB_REF_NAME): vX.Y.Z or vX.Y.Z-rc -# 4) git describe tag fallback -VERSION_IN="${RELEASE_VERSION:-${VERSION:-}}" - -ref_name="${GITHUB_REF_NAME:-}" - -infer_from_ref() { - local r="$1" - if printf '%s' "${r}" | grep -Eq '^(dev|rc|version)/[0-9]+\.[0-9]+\.[0-9]+$'; then - printf '%s' "${r#*/}" - return 0 - fi - if printf '%s' "${r}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-rc)?$'; then - r="${r#v}" - r="${r%-rc}" - printf '%s' "${r}" - return 0 - fi - return 1 -} - -VERSION_RESOLVED="" - -if [ -n "${VERSION_IN}" ]; then - if ! printf '%s' "${VERSION_IN}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then - fail "Invalid version format in env" "\"version\":$(json_escape "${VERSION_IN}")" - fi - VERSION_RESOLVED="${VERSION_IN}" -else - if [ -n "${ref_name}" ]; then - if v="$(infer_from_ref "${ref_name}" 2>/dev/null)"; then - VERSION_RESOLVED="${v}" - fi - fi - - if [ -z "${VERSION_RESOLVED}" ]; then - tag="$(git describe --tags --abbrev=0 2>/dev/null || true)" - if [ -n "${tag}" ]; then - if v="$(infer_from_ref "${tag}" 2>/dev/null)"; then - VERSION_RESOLVED="${v}" - fi - fi - fi -fi - -if [ -z "${VERSION_RESOLVED}" ]; then - fail "Unable to infer version (set RELEASE_VERSION or VERSION, or use a versioned branch/tag)" "\"ref_name\":$(json_escape "${ref_name:-}" )" -fi - -if [ ! -f "CHANGELOG.md" ]; then - fail "CHANGELOG.md missing" -fi - -if [ ! -s "CHANGELOG.md" ]; then - fail "CHANGELOG.md is empty" -fi - -# Core structural checks -# - Must contain at least one H2 heading with a bracketed version -# - Must contain a section for the resolved version - -if ! grep -Eq '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' CHANGELOG.md; then - fail "CHANGELOG.md has no version sections (expected headings like: ## [x.y.z])" -fi - -# Version section existence -if ! grep -Fq "## [${VERSION_RESOLVED}]" CHANGELOG.md; then - fail "CHANGELOG.md missing version section" "\"version\":$(json_escape "${VERSION_RESOLVED}")" -fi - -# Optional quality checks (warnings only) -warnings=() - -# Expect a date on the same line as the version heading, like: ## [x.y.z] YYYY-MM-DD -if ! grep -Eq "^## \[${VERSION_RESOLVED}\] [0-9]{4}-[0-9]{2}-[0-9]{2}$" CHANGELOG.md; then - warnings+=("version_heading_date_missing_or_nonstandard") -fi - -# Minimal section content: require at least one non-empty line between this version heading and the next heading. -python3 - <<'PY' "${VERSION_RESOLVED}" || true -import re,sys -ver = sys.argv[1] -text = open('CHANGELOG.md','r',encoding='utf-8').read().splitlines() -start = None -for i,line in enumerate(text): - if line.startswith(f"## [{ver}]"): - start = i - break -if start is None: - sys.exit(0) -end = len(text) -for j in range(start+1,len(text)): - if text[j].startswith('## ['): - end = j - break -block = [ln for ln in text[start+1:end] if ln.strip()] -# block contains at least one meaningful line (excluding blank) -if len(block) == 0: - print('WARN: version_section_empty') -PY - -if grep -Fq 'WARN: version_section_empty' <(python3 - <<'PY' "${VERSION_RESOLVED}" 2>/dev/null || true -import sys -ver = sys.argv[1] -lines = open('CHANGELOG.md','r',encoding='utf-8').read().splitlines() -start = None -for i,l in enumerate(lines): - if l.startswith(f"## [{ver}]"): - start=i - break -if start is None: - sys.exit(0) -end=len(lines) -for j in range(start+1,len(lines)): - if lines[j].startswith('## ['): - end=j - break -block=[ln for ln in lines[start+1:end] if ln.strip()] -if len(block)==0: - print('WARN: version_section_empty') -PY -); then - warnings+=("version_section_empty") -fi - -# Emit machine-readable report -if [ "${#warnings[@]}" -gt 0 ]; then - # Build JSON array safely - warn_json="[" - sep="" - for w in "${warnings[@]}"; do - warn_json+="${sep}$(json_escape "${w}")" - sep=","; - done - warn_json+="]" - ok "\"version\":$(json_escape "${VERSION_RESOLVED}"),\"ref_name\":$(json_escape "${ref_name:-}"),\"warnings\":${warn_json}" -else - ok "\"version\":$(json_escape "${VERSION_RESOLVED}"),\"ref_name\":$(json_escape "${ref_name:-}"),\"warnings\":[]" -fi - -printf '%s\n' "changelog: ok (version=${VERSION_RESOLVED})" diff --git a/scripts/validate/language_structure.sh b/scripts/validate/language_structure.sh deleted file mode 100755 index 753e971..0000000 --- a/scripts/validate/language_structure.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Joomla.Language -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/language_structure.sh -# VERSION: 01.00.00 -# BRIEF: Validates Joomla language directory structure and INI files -# NOTE: Ensures proper language file organization -# ============================================================================ - -set -euo pipefail - -SRC_DIR="${SRC_DIR:-src}" -LANG_ROOT="${LANG_ROOT:-${SRC_DIR}/language}" - -json_escape() { - python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1" -} - -[ -d "${SRC_DIR}" ] || { - printf '{"status":"fail","error":%s} -' "$(json_escape "src directory missing")" - exit 1 -} - -python3 - <<'PY' "${LANG_ROOT}" -import json -import sys -import re -from pathlib import Path - -lang_root = Path(sys.argv[1]) - -# Language directory is optional for some extension types -if not lang_root.exists(): - print(json.dumps({"status":"ok","lang_root":str(lang_root),"languages":[],"warnings":["language_root_missing"]}, ensure_ascii=False)) - sys.exit(0) - -if not lang_root.is_dir(): - print(json.dumps({"status":"fail","error":"language_root_not_directory","lang_root":str(lang_root)}, ensure_ascii=False)) - sys.exit(1) - -lang_dirs = sorted([p for p in lang_root.iterdir() if p.is_dir()]) - -# Joomla language tags: en-GB, fr-FR, etc. -pattern = re.compile(r'^[a-z]{2}-[A-Z]{2}$') -invalid = [p.name for p in lang_dirs if not pattern.match(p.name)] - -warnings = [] - -# Soft expectation: en-GB exists if any language directories exist -if lang_dirs and not (lang_root / 'en-GB').exists(): - warnings.append('en-GB_missing') - -# Validate INI naming -missing_ini = [] -nonmatching_ini = [] - -for d in lang_dirs: - ini_files = [p for p in d.glob('*.ini') if p.is_file()] - if not ini_files: - missing_ini.append(d.name) - continue - for ini in ini_files: - if not (ini.name.startswith(d.name + '.') or ini.name == f"{d.name}.ini"): - nonmatching_ini.append(str(ini)) - -result = { - "status": "ok", - "lang_root": str(lang_root), - "languages": [d.name for d in lang_dirs], - "warnings": warnings, -} - -# Hard failures -if invalid: - result.update({"status":"fail","error":"invalid_language_tag_dir","invalid":invalid}) - print(json.dumps(result, ensure_ascii=False)) - sys.exit(1) - -if nonmatching_ini: - result.update({"status":"fail","error":"ini_name_mismatch","nonmatching_ini":nonmatching_ini[:50]}) - print(json.dumps(result, ensure_ascii=False)) - sys.exit(1) - -if missing_ini: - result.update({"status":"fail","error":"missing_ini_files","missing_ini":missing_ini}) - print(json.dumps(result, ensure_ascii=False)) - sys.exit(1) - -print(json.dumps(result, ensure_ascii=False)) -PY - -echo "language_structure: ok" diff --git a/scripts/validate/license_headers.sh b/scripts/validate/license_headers.sh deleted file mode 100755 index 75cfdf1..0000000 --- a/scripts/validate/license_headers.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Licensing -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/license_headers.sh -# VERSION: 01.00.00 -# BRIEF: Checks that source files contain SPDX license identifiers -# NOTE: Ensures licensing compliance across codebase -# ============================================================================ - -set -euo pipefail - -SRC_DIR="${SRC_DIR:-src}" - -json_escape() { - python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1" -} - -[ -d "${SRC_DIR}" ] || { - printf '{"status":"fail","error":%s}\n' "$(json_escape "src directory missing")" - exit 1 -} - -python3 - <<'PY' "${SRC_DIR}" -import json -import sys -from pathlib import Path - -src = Path(sys.argv[1]) - -exts = {'.php','.js','.css','.sh','.yml','.yaml','.xml'} -exclude_dirs = {'vendor','node_modules','dist','.git','build','tmp'} - -missing = [] -scanned = 0 - -for p in src.rglob('*'): - if not p.is_file(): - continue - if any(part in exclude_dirs for part in p.parts): - continue - if p.suffix.lower() not in exts: - continue - - try: - data = p.read_bytes()[:2048] - except Exception: - continue - - if b'\x00' in data: - continue - - scanned += 1 - head = data.decode('utf-8', errors='replace') - if 'SPDX-License-Identifier:' not in head: - missing.append(str(p)) - -if missing: - print(json.dumps({ - "status":"fail", - "error":"missing_spdx_identifier", - "scanned":scanned, - "missing_count":len(missing), - "missing":missing[:200] - }, ensure_ascii=False)) - sys.exit(1) - -print(json.dumps({"status":"ok","scanned":scanned,"missing_count":0}, ensure_ascii=False)) -PY - -echo "license_headers diff --git a/scripts/validate/manifest.sh b/scripts/validate/manifest.sh deleted file mode 100755 index 1574adc..0000000 --- a/scripts/validate/manifest.sh +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Joomla.Manifest -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/manifest.sh -# VERSION: 01.00.00 -# BRIEF: Validates Joomla manifest XML structure and required fields -# NOTE: Ensures extension manifest compliance -# ============================================================================ - -set -euo pipefail - -# Input validation -SRC_DIR="${SRC_DIR:-src}" - -log() { printf '%s\n' "$*"; } - -fail() { - log "ERROR: $*" >&2 - exit 1 -} - -# Validate SRC_DIR -if [ ! -d "${SRC_DIR}" ]; then - fail "${SRC_DIR} directory missing. Set SRC_DIR environment variable or ensure 'src' directory exists." -fi - -# Validate required dependencies -if ! command -v python3 >/dev/null 2>&1; then - fail "python3 is required but not found. Please install Python 3." -fi - -# Candidate discovery policy: prefer explicit known names, otherwise fall back to extension-root manifests. -# Goal: choose ONE manifest deterministically. -manifest_candidates=() - -# Template -if [ -f "${SRC_DIR}/templateDetails.xml" ]; then - manifest_candidates+=("${SRC_DIR}/templateDetails.xml") -fi - -# Package -while IFS= read -r f; do - [ -n "${f}" ] && manifest_candidates+=("${f}") -done < <(find "${SRC_DIR}" -maxdepth 4 -type f -name 'pkg_*.xml' 2>/dev/null | sort || true) - -# Component -while IFS= read -r f; do - [ -n "${f}" ] && manifest_candidates+=("${f}") -done < <(find "${SRC_DIR}" -maxdepth 4 -type f -name 'com_*.xml' 2>/dev/null | sort || true) - -# Module -while IFS= read -r f; do - [ -n "${f}" ] && manifest_candidates+=("${f}") -done < <(find "${SRC_DIR}" -maxdepth 4 -type f -name 'mod_*.xml' 2>/dev/null | sort || true) - -# Plugin -while IFS= read -r f; do - [ -n "${f}" ] && manifest_candidates+=("${f}") -done < <(find "${SRC_DIR}" -maxdepth 6 -type f -name 'plg_*.xml' 2>/dev/null | sort || true) - -# Fallback: any XML containing -if [ "${#manifest_candidates[@]}" -eq 0 ]; then - while IFS= read -r f; do - [ -n "${f}" ] && manifest_candidates+=("${f}") - done < <(grep -Rsl --include='*.xml' '/dev/null | sort || true) -fi - -if [ "${#manifest_candidates[@]}" -eq 0 ]; then - { - echo "ERROR: No Joomla manifest XML found under ${SRC_DIR}" >&2 - echo "" >&2 - echo "Expected manifest file patterns:" >&2 - echo " - Template: ${SRC_DIR}/templateDetails.xml" >&2 - echo " - Package: ${SRC_DIR}/**/pkg_*.xml" >&2 - echo " - Component: ${SRC_DIR}/**/com_*.xml" >&2 - echo " - Module: ${SRC_DIR}/**/mod_*.xml" >&2 - echo " - Plugin: ${SRC_DIR}/**/plg_*.xml" >&2 - echo "" >&2 - echo "Troubleshooting:" >&2 - echo " 1. Verify the source directory exists: ls -la ${SRC_DIR}" >&2 - echo " 2. Check for XML files: find ${SRC_DIR} -name '*.xml'" >&2 - echo " 3. Ensure manifest contains root element" >&2 - echo "" >&2 - } >&2 - fail "No manifest found" -fi - -# De-duplicate while preserving order. -unique_candidates=() -for c in "${manifest_candidates[@]}"; do - seen=false - for u in "${unique_candidates[@]}"; do - if [ "${u}" = "${c}" ]; then - seen=true - break - fi - done - if [ "${seen}" = "false" ]; then - unique_candidates+=("${c}") - fi -done -manifest_candidates=("${unique_candidates[@]}") - -# Enforce single primary manifest. -if [ "${#manifest_candidates[@]}" -gt 1 ]; then - { - log "ERROR: Multiple manifest candidates detected. Resolve to exactly one primary manifest." >&2 - log "" >&2 - log "Found ${#manifest_candidates[@]} candidates:" >&2 - for c in "${manifest_candidates[@]}"; do - log " - ${c}" >&2 - done - log "" >&2 - log "Resolution options:" >&2 - log " 1. Remove redundant manifest files" >&2 - log " 2. Move extra manifests outside ${SRC_DIR}" >&2 - log " 3. Rename non-primary manifests to not match patterns (templateDetails.xml, pkg_*.xml, etc.)" >&2 - log "" >&2 - log "For package extensions, only the top-level package manifest should be in ${SRC_DIR}." >&2 - log "Child extension manifests should be in subdirectories." >&2 - log "" >&2 - } >&2 - exit 1 -fi - -MANIFEST="${manifest_candidates[0]}" - -if [ ! -s "${MANIFEST}" ]; then - fail "Manifest is empty: ${MANIFEST}" -fi - -# Parse with python for portability (xmllint not guaranteed). -python3 - <<'PY' "${MANIFEST}" || exit 1 -import sys -import json -import xml.etree.ElementTree as ET -from pathlib import Path - -manifest_path = Path(sys.argv[1]) - -def fail(msg, **ctx): - payload = {"status":"fail","error":msg, **ctx} - print(json.dumps(payload, ensure_ascii=False)) - sys.exit(1) - -try: - tree = ET.parse(manifest_path) - root = tree.getroot() -except Exception as e: - fail("XML parse failed", manifest=str(manifest_path), detail=str(e)) - -if root.tag != "extension": - fail("Root element must be ", manifest=str(manifest_path), root=str(root.tag)) - -ext_type = (root.attrib.get("type") or "").strip().lower() or "unknown" -allowed_types = {"template","component","module","plugin","package","library","file","files"} - -# Minimal required fields across most extension types. -name_el = root.find("name") -version_el = root.find("version") - -name = (name_el.text or "").strip() if name_el is not None else "" -version = (version_el.text or "").strip() if version_el is not None else "" - -missing = [] -if not name: - missing.append("name") -if not version: - missing.append("version") - -if ext_type not in allowed_types and ext_type != "unknown": - fail("Unsupported extension type", manifest=str(manifest_path), ext_type=ext_type) - -# Type-specific expectations. -warnings = [] - -if ext_type == "plugin": - group = (root.attrib.get("group") or "").strip() - if not group: - missing.append("plugin.group") - - files_el = root.find("files") - if files_el is None: - missing.append("files") - -elif ext_type in {"component","module","template"}: - files_el = root.find("files") - if files_el is None: - missing.append("files") - -elif ext_type == "package": - files_el = root.find("files") - if files_el is None: - missing.append("files") - else: - # Package should reference at least one child manifest. - file_nodes = files_el.findall("file") - if not file_nodes: - warnings.append("package.files has no entries") - -# Optional but commonly expected. -method = (root.attrib.get("method") or "").strip().lower() -if method and method not in {"upgrade","install"}: - warnings.append(f"unexpected extension method={method}") - -# Provide a stable, machine-readable report. -if missing: - fail("Missing required fields", manifest=str(manifest_path), ext_type=ext_type, missing=missing, warnings=warnings) - -print(json.dumps({ - "status": "ok", - "manifest": str(manifest_path), - "ext_type": ext_type, - "name": name, - "version": version, - "warnings": warnings, -}, ensure_ascii=False)) -PY - -# Human-friendly summary (kept short for CI logs). -log "manifest: ok (${MANIFEST})" diff --git a/scripts/validate/no_secrets.sh b/scripts/validate/no_secrets.sh deleted file mode 100755 index 4b50045..0000000 --- a/scripts/validate/no_secrets.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Security -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/no_secrets.sh -# VERSION: 01.00.00 -# BRIEF: Scan for accidentally committed secrets and credentials -# NOTE: High-signal pattern detection to prevent credential exposure -# ============================================================================ - -set -euo pipefail - -SRC_DIR="${SRC_DIR:-src}" - -json_escape() { - python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1" -} - -[ -d "${SRC_DIR}" ] || { - printf '{"status":"fail","error":%s} -' "$(json_escape "src directory missing")" - exit 1 -} - -# High-signal patterns only. Any match is a hard fail. -patterns=( - '-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----' - 'PuTTY-User-Key-File-' - 'AKIA[0-9A-Z]{16}' - 'ASIA[0-9A-Z]{16}' - 'ghp_[A-Za-z0-9]{36}' - 'gho_[A-Za-z0-9]{36}' - 'github_pat_[A-Za-z0-9_]{20,}' - 'xox[baprs]-[0-9A-Za-z-]{10,48}' - 'sk_live_[0-9a-zA-Z]{20,}' -) - -regex="$(IFS='|'; echo "${patterns[*]}")" - -set +e -hits=$(grep -RInE --exclude-dir=vendor --exclude-dir=node_modules --exclude-dir=dist "${regex}" "${SRC_DIR}" 2>/dev/null) -set -e - -if [ -n "${hits}" ]; then - { - echo '{"status":"fail","error":"secret_pattern_detected","hits":[' - echo "${hits}" | head -n 50 | python3 - <<'PY' -import json,sys -lines=[l.rstrip(' -') for l in sys.stdin.readlines() if l.strip()] -print(" -".join([json.dumps({"hit":l})+"," for l in lines]).rstrip(',')) -PY - echo ']}' - } - exit 1 -fi - -printf '{"status":"ok","src_dir":%s} -' "$(json_escape "${SRC_DIR}")" -echo "no_secrets: ok" diff --git a/scripts/validate/paths.sh b/scripts/validate/paths.sh deleted file mode 100755 index 1a7f0b5..0000000 --- a/scripts/validate/paths.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Path.Normalization -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/paths.sh -# VERSION: 01.00.00 -# BRIEF: Detect Windows-style path separators (backslashes) -# NOTE: Ensures cross-platform path compatibility -# ============================================================================ - -set -euo pipefail - -# Detect Windows-style path literals (backslashes) in repository files. -# Uses git ls-files -z and searches file contents for a literal backslash. - -hits=() -hit_lines=() - -while IFS= read -r -d '' f; do - # Skip common binary files by mime-type - if file --brief --mime-type "$f" | grep -qE '^(application|audio|image|video)/'; then - continue - fi - # Find lines with backslashes and collect details - if backslash_lines=$(grep -n -F $'\\' -- "$f" 2>/dev/null); then - hits+=("$f") - hit_lines+=("$backslash_lines") - fi -done < <(git ls-files -z) - -if [ "${#hits[@]}" -gt 0 ]; then - echo "ERROR: Windows-style path literals detected" >&2 - echo "" >&2 - echo "Found backslashes in ${#hits[@]} file(s):" >&2 - for i in "${!hits[@]}"; do - echo "" >&2 - echo " File: ${hits[$i]}" >&2 - echo " Lines with backslashes:" >&2 - echo "${hit_lines[$i]}" | head -5 | sed 's/^/ /' >&2 - if [ "$(echo "${hit_lines[$i]}" | wc -l)" -gt 5 ]; then - echo " ... and $(($(echo "${hit_lines[$i]}" | wc -l) - 5)) more" >&2 - fi - done - echo "" >&2 - echo "To fix:" >&2 - echo " 1. Run: ./scripts/fix/paths.sh" >&2 - echo " 2. Or manually replace backslashes (\\) with forward slashes (/)" >&2 - echo " 3. Ensure paths use POSIX separators for cross-platform compatibility" >&2 - echo "" >&2 - exit 2 -fi - -echo "paths: ok" diff --git a/scripts/validate/php_syntax.sh b/scripts/validate/php_syntax.sh deleted file mode 100755 index cbb6988..0000000 --- a/scripts/validate/php_syntax.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Code.Quality -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/php_syntax.sh -# VERSION: 01.00.00 -# BRIEF: Validates PHP syntax using php -l on all PHP files -# NOTE: Requires PHP CLI to be available -# ============================================================================ - -set -euo pipefail - -# Validation timeout (seconds) - prevents hanging on problematic files -TIMEOUT="${VALIDATION_TIMEOUT:-30}" -SRC_DIR="${SRC_DIR:-src}" - -json_escape() { - python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1" -} - -[ -d "${SRC_DIR}" ] || { - printf '{"status":"fail","error":%s} -' "$(json_escape "src directory missing")" - exit 1 -} - -if ! command -v php >/dev/null 2>&1; then - printf '{"status":"ok","warning":"php_not_available","src_dir":%s} -' "$(json_escape "${SRC_DIR}")" - echo "php_syntax: ok (php not available)" - exit 0 -fi - -failed=0 -checked=0 -failed_files=() -failed_errors=() - -while IFS= read -r -d '' f; do - checked=$((checked+1)) - - # Capture actual error output - error_output="" - - # Use timeout if available to prevent hangs - if command -v timeout >/dev/null 2>&1; then - if ! error_output=$(timeout "${TIMEOUT}" php -l "$f" 2>&1); then - failed=1 - failed_files+=("$f") - failed_errors+=("$error_output") - fi - else - if ! error_output=$(php -l "$f" 2>&1); then - failed=1 - failed_files+=("$f") - failed_errors+=("$error_output") - fi - fi -done < <(find "${SRC_DIR}" -type f -name '*.php' -print0) - -if [ "${failed}" -ne 0 ]; then - echo "ERROR: PHP syntax validation failed" >&2 - echo "Files checked: ${checked}" >&2 - echo "Files with errors: ${#failed_files[@]}" >&2 - echo "" >&2 - echo "Failed files and errors:" >&2 - for i in "${!failed_files[@]}"; do - echo " File: ${failed_files[$i]}" >&2 - echo " Error: ${failed_errors[$i]}" >&2 - echo "" >&2 - done - echo "" >&2 - echo "To fix: Review and correct the syntax errors in the files listed above." >&2 - echo "Run 'php -l ' on individual files for detailed error messages." >&2 - { - printf '{"status":"fail","error":"php_lint_failed","files_checked":%s,"failed_count":%s,"failed_files":[' "${checked}" "${#failed_files[@]}" - for i in "${!failed_files[@]}"; do - printf '%s' "$(json_escape "${failed_files[$i]}")" - [ "$i" -lt $((${#failed_files[@]} - 1)) ] && printf ',' - done - printf ']}\n' - } - exit 1 -fi - -printf '{"status":"ok","files_checked":%s} -' "${checked}" -echo "php_syntax: ok" diff --git a/scripts/validate/tabs.sh b/scripts/validate/tabs.sh deleted file mode 100755 index 0798ba9..0000000 --- a/scripts/validate/tabs.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Code.Quality -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/tabs.sh -# VERSION: 01.00.00 -# BRIEF: Detect TAB characters in YAML files where they are not allowed -# NOTE: YAML specification forbids tab characters -# ============================================================================ - -set -euo pipefail - -# Detect TAB characters in source files tracked by Git. Uses careful -# handling of filenames and avoids heredoc pitfalls. - -# Check only YAML/YML files where tabs are not allowed by the YAML specification. -# Note: Other file types (PHP, JS, etc.) allow tabs per .editorconfig. -files=$(git ls-files '*.yml' '*.yaml' || true) - -if [ -z "${files}" ]; then - echo "No files to check" - exit 0 -fi - -bad=0 -bad_files=() -bad_lines=() - -while IFS= read -r f; do - # Find lines with tabs and store them - if tab_lines=$(grep -n $'\t' -- "$f" 2>/dev/null); then - echo "TAB found in $f" >&2 - echo " Lines with tabs:" >&2 - echo "$tab_lines" | head -5 | sed 's/^/ /' >&2 - if [ "$(echo "$tab_lines" | wc -l)" -gt 5 ]; then - echo " ... and $(($(echo "$tab_lines" | wc -l) - 5)) more" >&2 - fi - echo "" >&2 - bad=1 - bad_files+=("$f") - fi -done <<< "${files}" - -if [ "${bad}" -ne 0 ]; then - echo "" >&2 - echo "ERROR: Tabs found in repository files" >&2 - echo "" >&2 - echo "YAML specification forbids tab characters." >&2 - echo "Found tabs in ${#bad_files[@]} file(s):" >&2 - for f in "${bad_files[@]}"; do - echo " - $f" >&2 - done - echo "" >&2 - echo "To fix:" >&2 - echo " 1. Run: ./scripts/fix/tabs.sh" >&2 - echo " 2. Or manually replace tabs with spaces in your editor" >&2 - echo " 3. Configure your editor to use spaces (not tabs) for YAML files" >&2 - echo "" >&2 - exit 2 -fi - -echo "tabs: ok" diff --git a/scripts/validate/version_alignment.sh b/scripts/validate/version_alignment.sh deleted file mode 100755 index 34767dd..0000000 --- a/scripts/validate/version_alignment.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Version.Management -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/version_alignment.sh -# VERSION: 01.00.00 -# BRIEF: Checks that manifest version is documented in CHANGELOG.md -# NOTE: Ensures version consistency across repository -# ============================================================================ - -set -euo pipefail - -# Validate that the package/manifest version is present in CHANGELOG.md -# Uses a safe, quoted heredoc for the embedded Python to avoid shell -# interpolation and CRLF termination issues. - -if ! command -v python3 >/dev/null 2>&1; then - echo "ERROR: python3 not found" >&2 - exit 1 -fi - -python3 - <<'PY' -import sys, re, json, glob - -# Locate a likely manifest under src -candidates = [ - 'src/templateDetails.xml', - 'src/manifest.xml' -] -manifest = None -for p in candidates: - try: - with open(p, 'r', encoding='utf-8') as fh: - manifest = fh.read() - break - except FileNotFoundError: - pass - -if manifest is None: - # Fallback: search for an XML file under src that contains a version attribute - for fn in glob.glob('src/**/*.xml', recursive=True): - try: - with open(fn, 'r', encoding='utf-8') as fh: - txt = fh.read() - if 'version=' in txt: - manifest = txt - break - except Exception: - continue - -if manifest is None: - print('WARNING: No manifest found, skipping version alignment check') - sys.exit(0) - -m = re.search(r'version=["\']([0-9]+\.[0-9]+\.[0-9]+)["\']', manifest) -if not m: - print('ERROR: could not find semantic version in manifest') - sys.exit(2) - -manifest_version = m.group(1) - -try: - with open('CHANGELOG.md', 'r', encoding='utf-8') as fh: - changelog = fh.read() -except FileNotFoundError: - print('ERROR: CHANGELOG.md not found') - sys.exit(2) - -if f'## [{manifest_version}]' not in changelog: - print(f'ERROR: version {manifest_version} missing from CHANGELOG.md') - sys.exit(2) - -print(json.dumps({'status': 'ok', 'version': manifest_version})) -sys.exit(0) -PY diff --git a/scripts/validate/version_hierarchy.sh b/scripts/validate/version_hierarchy.sh deleted file mode 100755 index e692735..0000000 --- a/scripts/validate/version_hierarchy.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: Version.Management -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/version_hierarchy.sh -# VERSION: 01.00.00 -# BRIEF: Validate version hierarchy across branch prefixes -# NOTE: Checks for version conflicts across dev/, rc/, and version/ branches -# ============================================================================ - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# ---------------------------------------------------------------------------- -# Functions -# ---------------------------------------------------------------------------- - -check_version_hierarchy() { - log_info "Checking version hierarchy across branch prefixes..." - - local violations=0 - - # Get all branches with version-like names - local branches - branches=$(git ls-remote --heads origin 2>/dev/null | awk '{print $2}' | sed 's|refs/heads/||' || echo "") - - if [ -z "$branches" ]; then - log_warn "No remote branches found or unable to fetch branches" - return 0 - fi - - # Extract versions from branches - local dev_versions=() - local rc_versions=() - local stable_versions=() - - while IFS= read -r branch; do - if [[ "$branch" =~ ^dev/([0-9]{2}\.[0-9]{2}\.[0-9]{2})$ ]]; then - dev_versions+=("${BASH_REMATCH[1]}") - elif [[ "$branch" =~ ^rc/([0-9]{2}\.[0-9]{2}\.[0-9]{2})$ ]]; then - rc_versions+=("${BASH_REMATCH[1]}") - elif [[ "$branch" =~ ^version/([0-9]{2}\.[0-9]{2}\.[0-9]{2})$ ]]; then - stable_versions+=("${BASH_REMATCH[1]}") - fi - done <<< "$branches" - - log_info "Found ${#dev_versions[@]} dev versions, ${#rc_versions[@]} RC versions, ${#stable_versions[@]} stable versions" - - # Check for violations: - # 1. dev/ version that exists in rc/ or version/ - for version in "${dev_versions[@]}"; do - # Check if exists in stable - for stable in "${stable_versions[@]}"; do - if [ "$version" = "$stable" ]; then - log_error "VIOLATION: Version $version exists in both dev/ and version/ branches" - violations=$((violations + 1)) - fi - done - - # Check if exists in RC - for rc in "${rc_versions[@]}"; do - if [ "$version" = "$rc" ]; then - log_error "VIOLATION: Version $version exists in both dev/ and rc/ branches" - violations=$((violations + 1)) - fi - done - done - - # 2. rc/ version that exists in version/ - for version in "${rc_versions[@]}"; do - for stable in "${stable_versions[@]}"; do - if [ "$version" = "$stable" ]; then - log_error "VIOLATION: Version $version exists in both rc/ and version/ branches" - violations=$((violations + 1)) - fi - done - done - - if [ $violations -eq 0 ]; then - log_info "✓ No version hierarchy violations found" - return 0 - else - log_error "✗ Found $violations version hierarchy violation(s)" - return 1 - fi -} - -# ---------------------------------------------------------------------------- -# Main -# ---------------------------------------------------------------------------- - -log_info "Version Hierarchy Validation" -log_info "=============================" -log_info "" - -check_version_hierarchy -exit_code=$? - -log_info "" -log_info "=============================" -if [ $exit_code -eq 0 ]; then - log_info "Version hierarchy validation passed" -else - log_error "Version hierarchy validation failed" -fi - -exit $exit_code diff --git a/scripts/validate/workflows.sh b/scripts/validate/workflows.sh deleted file mode 100755 index aad70d9..0000000 --- a/scripts/validate/workflows.sh +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: CI.Validation -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/workflows.sh -# VERSION: 01.00.00 -# BRIEF: Validate GitHub Actions workflow files -# NOTE: Checks YAML syntax, structure, and best practices -# ============================================================================ - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -. "${SCRIPT_DIR}/lib/common.sh" - -# ---------------------------------------------------------------------------- -# Functions -# ---------------------------------------------------------------------------- - -validate_yaml_syntax() { - local file="$1" - - if ! command -v python3 >/dev/null 2>&1; then - log_warn "python3 not found, skipping YAML syntax validation" - return 0 - fi - - python3 - "$file" <<'PYEOF' -import sys - -try: - import yaml -except ModuleNotFoundError: - print("WARNING: PyYAML module not installed. Install with: pip3 install pyyaml") - sys.exit(0) - -file_path = sys.argv[1] - -try: - with open(file_path, 'r') as f: - yaml.safe_load(f) - print(f"✓ Valid YAML: {file_path}") - sys.exit(0) -except yaml.YAMLError as e: - print(f"✗ YAML Error in {file_path}: {e}", file=sys.stderr) - sys.exit(1) -except Exception as e: - print(f"✗ Error reading {file_path}: {e}", file=sys.stderr) - sys.exit(1) -PYEOF -} - -check_no_tabs() { - local file="$1" - - if grep -q $'\t' "$file"; then - log_error "✗ File contains tab characters: $file" - return 1 - fi - - return 0 -} - -check_workflow_structure() { - local file="$1" - local filename=$(basename "$file") - - # Check for required top-level keys - if ! grep -q "^name:" "$file"; then - log_warn "Missing 'name:' in $filename" - fi - - if ! grep -q "^on:" "$file"; then - log_error "✗ Missing 'on:' trigger in $filename" - return 1 - fi - - if ! grep -q "^jobs:" "$file"; then - log_error "✗ Missing 'jobs:' in $filename" - return 1 - fi - - return 0 -} - -validate_workflow_file() { - local file="$1" - local filename=$(basename "$file") - - log_info "Validating: $filename" - - local errors=0 - - # Check YAML syntax - if ! validate_yaml_syntax "$file"; then - errors=$((errors + 1)) - fi - - # Check for tabs - if ! check_no_tabs "$file"; then - errors=$((errors + 1)) - fi - - # Check structure - if ! check_workflow_structure "$file"; then - errors=$((errors + 1)) - fi - - if [ $errors -eq 0 ]; then - log_info "✓ $filename passed all checks" - return 0 - else - log_error "✗ $filename failed $errors check(s)" - return 1 - fi -} - -# ---------------------------------------------------------------------------- -# Main -# ---------------------------------------------------------------------------- - -log_info "GitHub Actions Workflow Validation" -log_info "===================================" -log_info "" - -WORKFLOWS_DIR=".github/workflows" - -if [ ! -d "$WORKFLOWS_DIR" ]; then - log_error "Workflows directory not found: $WORKFLOWS_DIR" - exit 1 -fi - -total=0 -passed=0 -failed=0 - -for workflow in "$WORKFLOWS_DIR"/*.yml "$WORKFLOWS_DIR"/*.yaml; do - if [ ! -f "$workflow" ]; then - continue - fi - - total=$((total + 1)) - - if validate_workflow_file "$workflow"; then - passed=$((passed + 1)) - else - failed=$((failed + 1)) - fi - - echo "" -done - -log_info "===================================" -log_info "Summary:" -log_info " Total workflows: $total" -log_info " Passed: $passed" -log_info " Failed: $failed" -log_info "===================================" - -if [ $failed -gt 0 ]; then - log_error "Workflow validation failed" - exit 1 -fi - -log_info "All workflows validated successfully" -exit 0 diff --git a/scripts/validate/xml_wellformed.sh b/scripts/validate/xml_wellformed.sh deleted file mode 100755 index fae5968..0000000 --- a/scripts/validate/xml_wellformed.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash - -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# 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.Validate -# INGROUP: XML.Validation -# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia -# PATH: /scripts/validate/xml_wellformed.sh -# VERSION: 01.00.00 -# BRIEF: Validates that all XML files are well-formed -# NOTE: Uses Python ElementTree for portable XML parsing -# ============================================================================ - -set -euo pipefail - -SRC_DIR="${SRC_DIR:-src}" - -json_escape() { - python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1" -} - -fail() { - local msg="$1" - local extra="${2:-}" - if [ -n "${extra}" ]; then - printf '{"status":"fail","error":%s,%s}\n' "$(json_escape "${msg}")" "${extra}" - else - printf '{"status":"fail","error":%s}\n' "$(json_escape "${msg}")" - fi - exit 1 -} - -[ -d "${SRC_DIR}" ] || fail "src directory missing" "\"src_dir\":$(json_escape "${SRC_DIR}")" - -python3 - <<'PY' "${SRC_DIR}" -import json -import sys -from pathlib import Path -import xml.etree.ElementTree as ET - -src = Path(sys.argv[1]) -xml_files = sorted([p for p in src.rglob('*.xml') if p.is_file()]) - -bad = [] -for p in xml_files: - try: - ET.parse(p) - except Exception as e: - bad.append({"path": str(p), "error": str(e)}) - -if bad: - print(json.dumps({ - "status": "fail", - "error": "XML parse failed", - "src_dir": str(src), - "xml_count": len(xml_files), - "bad_count": len(bad), - "bad": bad[:25], - }, ensure_ascii=False)) - sys.exit(1) - -print(json.dumps({ - "status": "ok", - "src_dir": str(src), - "xml_count": len(xml_files), -}, ensure_ascii=False)) -PY - -echo "xml_wellformed: ok" -- 2.49.1 From a90461f503a054e80fed5ae85daeb0037fa0e755 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:23:16 +0000 Subject: [PATCH 6/8] Update documentation for Python scripts and Dolibarr support Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- README.md | 12 ++++++++++-- scripts/{README.md => README.md.old} | 0 2 files changed, 10 insertions(+), 2 deletions(-) rename scripts/{README.md => README.md.old} (100%) diff --git a/README.md b/README.md index d5c2e24..295cee5 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ make quality make package # Install Git hooks (optional but recommended) -./scripts/git/install-hooks.sh +python3 ./scripts/git/install-hooks.py ``` **New to the project?** See [Quick Start Guide](./docs/QUICK_START.md) for a 5-minute walkthrough. @@ -168,15 +168,23 @@ make package ### Available Tools - **Makefile**: Run `make help` to see all available commands +- **Python Scripts**: All automation scripts are now Python-based for cross-platform compatibility - **Pre-commit Hooks**: Automatic validation before commits - **VS Code Tasks**: Pre-configured development tasks - **GitHub Actions**: Automated CI/CD pipelines +### Cross-Platform Support + +All scripts are now written in Python for maximum cross-platform compatibility: +- **Joomla Extension Support**: Full support for Joomla 4.x and 5.x templates, components, modules, and plugins +- **Dolibarr Module Support**: Automatic detection and packaging of Dolibarr modules +- **Platform Detection**: Scripts automatically detect whether you're working with Joomla or Dolibarr extensions + ### Joomla Development Workflows Comprehensive Joomla-aware development tools and workflows are available: -- **Extension Packaging** - Create distributable ZIP packages +- **Extension Packaging** - Create distributable ZIP packages for Joomla or Dolibarr - **PHP Quality Checks** - PHPStan and PHP_CodeSniffer with Joomla standards - **Automated Testing** - Codeception framework with multiple Joomla versions - **CI/CD Pipelines** - GitHub Actions with caching for faster builds diff --git a/scripts/README.md b/scripts/README.md.old similarity index 100% rename from scripts/README.md rename to scripts/README.md.old -- 2.49.1 From 22bf6894c926a26383cb3393b6b5b4f907cef66c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:23:38 +0000 Subject: [PATCH 7/8] Fix scripts README.md Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- scripts/README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/README.md diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..a834e7b --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,75 @@ +# Scripts Documentation + +All automation scripts for the moko-cassiopeia project are written in Python for cross-platform compatibility and support both Joomla and Dolibarr extensions. + +## Quick Reference + +```bash +# Run all validations +make validate +python3 scripts/run/validate_all.py + +# Run specific validations +python3 scripts/validate/manifest.py +python3 scripts/validate/xml_wellformed.py + +# Create distribution package (auto-detects Joomla or Dolibarr) +make package +python3 scripts/release/package_extension.py dist +``` + +## Platform Support + +All scripts automatically detect and support: +- **Joomla Extensions**: Templates, Components, Modules, Plugins, Packages +- **Dolibarr Modules**: Automatic detection and packaging + +## Available Scripts + +### Validation Scripts (`scripts/validate/`) +- `manifest.py` - Validate extension manifests (Joomla/Dolibarr) +- `xml_wellformed.py` - Validate XML syntax +- `workflows.py` - Validate GitHub Actions workflows +- `tabs.py` - Check for tab characters in YAML +- `no_secrets.py` - Scan for secrets/credentials +- `paths.py` - Check for Windows-style paths +- `php_syntax.py` - Validate PHP syntax + +### Release Scripts (`scripts/release/`) +- `package_extension.py` - Create distributable ZIP packages + +### Run Scripts (`scripts/run/`) +- `validate_all.py` - Run all validation scripts +- `scaffold_extension.py` - Create new extension scaffolding + +### Library Scripts (`scripts/lib/`) +- `common.py` - Common utilities +- `joomla_manifest.py` - Joomla manifest parsing +- `dolibarr_manifest.py` - Dolibarr module parsing +- `extension_utils.py` - Unified extension detection + +## Requirements + +- Python 3.6+ +- Git +- PHP (for PHP syntax validation) + +## Migration from Shell Scripts + +All shell scripts have been converted to Python. Use Python equivalents: + +```bash +# Old (removed) # New +./scripts/validate/manifest.sh → python3 scripts/validate/manifest.py +./scripts/release/package.sh → python3 scripts/release/package_extension.py +``` + +For detailed documentation, see individual script help: +```bash +python3 scripts/validate/manifest.py --help +python3 scripts/release/package_extension.py --help +``` + +## License + +GPL-3.0-or-later - See [LICENSE](../LICENSE) -- 2.49.1 From f8568ffff5e580059fa12ffb9bbdecdad1296359 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:26:12 +0000 Subject: [PATCH 8/8] Convert shell scripts to Python with Joomla/Dolibarr platform support Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- scripts/README.md.old | 522 ------------------------------------------ 1 file changed, 522 deletions(-) delete mode 100644 scripts/README.md.old diff --git a/scripts/README.md.old b/scripts/README.md.old deleted file mode 100644 index 6f54042..0000000 --- a/scripts/README.md.old +++ /dev/null @@ -1,522 +0,0 @@ - - -# Scripts Directory - -This directory contains automation scripts for repository management, validation, -and release processes for the Moko-Cassiopeia Joomla template. - -## Directory Structure - -``` -scripts/ -├── fix/ # Scripts that automatically fix common issues -├── lib/ # Shared library functions -├── release/ # Release automation scripts -├── run/ # Execution and testing scripts -└── validate/ # Validation and linting scripts -``` - -## Library Files (`lib/`) - -### `common.sh` -Core utilities used by all scripts: -- Environment normalization -- Logging functions (`log_info`, `log_warn`, `log_error`, `die`) -- Validation helpers (`assert_file_exists`, `assert_dir_exists`) -- JSON utilities (`json_escape`, `json_output`) -- Path helpers (`script_root`, `normalize_path`) - -Usage: -```bash -. "$(dirname "$0")/../lib/common.sh" -log_info "Starting process..." -``` - -### `joomla_manifest.sh` -Joomla manifest parsing utilities: -- `find_manifest ` - Find primary Joomla manifest XML -- `get_manifest_version ` - Extract version from manifest -- `get_manifest_name ` - Extract extension name -- `get_manifest_type ` - Extract extension type - -Usage: -```bash -. "${SCRIPT_DIR}/lib/joomla_manifest.sh" -MANIFEST="$(find_manifest src)" -VERSION="$(get_manifest_version "${MANIFEST}")" -``` - -### `logging.sh` -Enhanced logging with structured output: -- Colored output support (when in terminal) -- Log levels: `log_debug`, `log_success`, `log_step` -- Structured logging: `log_kv`, `log_item`, `log_section` - -Usage: -```bash -. "${SCRIPT_DIR}/lib/logging.sh" -log_section "Starting validation" -log_kv "Version" "${VERSION}" -log_success "All checks passed" -``` - -## Validation Scripts (`validate/`) - -These scripts validate repository structure, code quality, and compliance. -They are typically run in CI pipelines. - -### `manifest.sh` -Validates Joomla manifest XML structure and required fields. - -### `version_alignment.sh` -Checks that manifest version is documented in CHANGELOG.md. - -### `php_syntax.sh` -Validates PHP syntax using `php -l` on all PHP files. - -### `xml_wellformed.sh` -Validates that all XML files are well-formed. - -### `tabs.sh` -Detects tab characters in source files (enforces spaces). - -### `paths.sh` -Detects Windows-style path separators (backslashes). - -### `no_secrets.sh` -Scans for accidentally committed secrets and credentials. - -### `license_headers.sh` -Checks that source files contain SPDX license identifiers. - -### `language_structure.sh` -Validates Joomla language directory structure and INI files. - -### `changelog.sh` -Validates CHANGELOG.md structure and version entries. - -### `version_hierarchy.sh` -Validates version hierarchy across branch prefixes: -- Checks for version conflicts across dev/, rc/, and version/ branches -- Ensures no version exists in multiple priority levels simultaneously -- Reports any violations of the hierarchy rules - -Usage: -```bash -./scripts/validate/version_hierarchy.sh -``` - -### `workflows.sh` -Validates GitHub Actions workflow files: -- Checks YAML syntax using Python's yaml module -- Ensures no tab characters are present -- Validates required workflow structure (name, on, jobs) - -Usage: -```bash -./scripts/validate/workflows.sh -``` - -## Fix Scripts (`fix/`) - -These scripts automatically fix common issues detected by validation scripts. - -### `tabs.sh` -Replaces tab characters with spaces in YAML files. - -Usage: -```bash -./scripts/fix/tabs.sh -``` - -### `paths.sh` -Normalizes Windows-style path separators to forward slashes. - -Usage: -```bash -./scripts/fix/paths.sh [directory] -``` - -### `versions.sh` -Updates version numbers across repository files. - -Usage: -```bash -./scripts/fix/versions.sh -``` - -Example: -```bash -./scripts/fix/versions.sh 3.5.0 -``` - -Updates: -- Manifest XML `` tag -- `package.json` version field -- Version references in README.md - -## Release Scripts (`release/`) - -Scripts for release automation and version management. - -### `update_changelog.sh` -Inserts a new version entry in CHANGELOG.md. - -Usage: -```bash -./scripts/release/update_changelog.sh -``` - -Example: -```bash -./scripts/release/update_changelog.sh 03.05.00 -``` - -### `update_dates.sh` -Normalizes release dates across manifests and CHANGELOG. - -Usage: -```bash -./scripts/release/update_dates.sh -``` - -Example: -```bash -./scripts/release/update_dates.sh 2025-01-15 03.05.00 -``` - -### `package_extension.sh` -Package the Joomla template as a distributable ZIP file. - -Usage: -```bash -./scripts/release/package_extension.sh [output_dir] [version] -``` - -Parameters: -- `output_dir` (optional): Output directory for ZIP file (default: `dist`) -- `version` (optional): Version string (default: extracted from manifest) - -Examples: -```bash -# Package with defaults (dist directory, auto-detect version) -./scripts/release/package_extension.sh - -# Package to specific directory with version -./scripts/release/package_extension.sh /tmp/packages 3.5.0 - -# Package to dist with specific version -./scripts/release/package_extension.sh dist 3.5.0 -``` - -Features: -- Automatically detects extension type from manifest -- Excludes development files (node_modules, vendor, tests, etc.) -- Validates manifest before packaging -- Creates properly structured Joomla installation package -- Outputs JSON status for automation - -## Run Scripts (`run/`) - -Execution and testing scripts. - -### `smoke_test.sh` -Runs basic smoke tests to verify repository health: -- Repository structure validation -- Manifest validation -- Version alignment checks -- PHP syntax validation - -Usage: -```bash -./scripts/run/smoke_test.sh -``` - -Example output: -``` -INFO: Running smoke tests for Moko-Cassiopeia repository -INFO: Checking repository structure... -INFO: ✓ Repository structure valid -INFO: Checking Joomla manifest... -INFO: Found manifest: src/templates/templateDetails.xml -INFO: Extension: moko-cassiopeia (template) v03.05.00 -INFO: ✓ Manifest validation passed -INFO: ========================================= -INFO: Smoke tests completed successfully -INFO: ========================================= -``` - -### `validate_all.sh` -Runs all validation scripts and provides a comprehensive report: -- Executes all required validation checks -- Executes all optional validation checks -- Provides colored output with pass/fail indicators -- Returns summary with counts -- Supports verbose mode for detailed output - -Usage: -```bash -./scripts/run/validate_all.sh # Standard mode -./scripts/run/validate_all.sh -v # Verbose mode (shows all output) -``` - -Example output: -``` -=== Repository Validation Suite === -INFO: Running all validation checks... - -=== Required Checks === -[SUCCESS] ✓ manifest -[SUCCESS] ✓ xml_wellformed - -=== Optional Checks === -[SUCCESS] ✓ no_secrets -[SUCCESS] ✓ php_syntax -WARN: ✗ tabs (warnings/issues found - run with -v for details) - -=== Validation Summary === - Required checks passed: 2/2 - Optional checks passed: 2/8 -[SUCCESS] SUCCESS: All required checks passed -``` - -### `script_health.sh` -Validates that all scripts follow enterprise standards: -- Checks for copyright headers -- Validates SPDX license identifiers -- Ensures FILE INFORMATION sections are present -- Verifies error handling (set -euo pipefail) -- Checks executable permissions - -Usage: -```bash -./scripts/run/script_health.sh # Standard mode -./scripts/run/script_health.sh -v # Verbose mode (shows details) -``` - -Example output: -``` -=== Script Health Summary === - Total scripts checked: 21 - Missing copyright: 0 - Missing SPDX identifier: 0 - Missing FILE INFORMATION: 0 - Missing error handling: 0 - Not executable: 0 -[SUCCESS] SUCCESS: All scripts follow enterprise standards -``` - -### `list_versions.sh` -Lists all version branches organized by prefix: -- Displays dev/, rc/, and version/ branches -- Shows versions sorted in ascending order -- Provides a summary count of each branch type - -Usage: -```bash -./scripts/run/list_versions.sh -``` - -Example output: -``` -======================================== -Version Branches Summary -======================================== - -📦 Stable Versions (version/) ----------------------------------------- - ✓ version/03.00.00 - ✓ version/03.01.00 - -🔧 Release Candidates (rc/) ----------------------------------------- - ➜ rc/03.02.00 - -🚧 Development Versions (dev/) ----------------------------------------- - ⚡ dev/03.05.00 - ⚡ dev/04.00.00 - -======================================== -Total: 2 stable, 1 RC, 2 dev -======================================== -``` - -### `check_version.sh` -Checks if a version can be created in a specific branch prefix: -- Validates against version hierarchy rules -- Checks for existing branches -- Reports conflicts with higher priority branches - -Usage: -```bash -./scripts/run/check_version.sh -``` - -Examples: -```bash -./scripts/run/check_version.sh dev/ 03.05.00 -./scripts/run/check_version.sh rc/ 03.01.00 -./scripts/run/check_version.sh version/ 02.00.00 -``` - -Exit codes: -- 0: Version can be created (no conflicts) -- 1: Version cannot be created (conflicts found) -- 2: Invalid arguments - -## Best Practices - -### Enterprise Standards - -For comprehensive enterprise-grade scripting standards, see -[ENTERPRISE.md](./ENTERPRISE.md). - -Key highlights: -- **Error Handling**: Fail fast with clear, actionable messages -- **Security**: Input validation, no hardcoded secrets -- **Logging**: Structured output with timestamps -- **Portability**: Cross-platform compatibility -- **Documentation**: Usage functions and inline comments - -### Writing New Scripts - -1. **Use the library functions**: - ```bash - #!/usr/bin/env bash - set -euo pipefail - - SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" - . "${SCRIPT_DIR}/lib/common.sh" - ``` - -2. **Include proper headers**: - - Copyright notice - - SPDX license identifier - - FILE INFORMATION section with DEFGROUP, INGROUP, PATH, VERSION, BRIEF - -3. **Follow error handling conventions**: - ```bash - [ -f "${FILE}" ] || die "File not found: ${FILE}" - require_cmd python3 - ``` - -4. **Use structured output**: - ```bash - log_info "Starting process..." - log_success "Process completed" - ``` - -5. **Make scripts executable**: - ```bash - chmod +x scripts/new-script.sh - ``` - -### Testing Scripts Locally - -Run all validation scripts: -```bash -./scripts/run/validate_all.sh -``` - -Run individual validation scripts: -```bash -./scripts/validate/manifest.sh -./scripts/validate/php_syntax.sh -./scripts/validate/tabs.sh -``` - -Run smoke tests: -```bash -./scripts/run/smoke_test.sh -``` - -### CI Integration - -Scripts are automatically executed in GitHub Actions workflows: -- `.github/workflows/ci.yml` - Continuous integration -- `.github/workflows/repo_health.yml` - Repository health checks - -## Enterprise Features - -The scripts in this repository follow enterprise-grade standards: - -### Dependency Checking - -Scripts validate required dependencies at startup using `check_dependencies`: - -```bash -check_dependencies python3 git sed -``` - -### Timestamp Logging - -All major operations include timestamps for audit trails: - -```bash -log_info "Start time: $(log_timestamp)" -``` - -### Usage Documentation - -All user-facing scripts include comprehensive help: - -```bash -./scripts/run/validate_all.sh --help -./scripts/fix/versions.sh --help -``` - -### Standardized Exit Codes - -- `0` - Success -- `1` - Fatal error -- `2` - Invalid arguments - -### Enhanced Error Messages - -Clear, actionable error messages with context: - -```bash -die "Required file not found: ${CONFIG_FILE}. Run setup first." -``` - -See [ENTERPRISE.md](./ENTERPRISE.md) for complete standards documentation. - -## Version History - -| Version | Date | Description | -| ------- | ---------- | ------------------------------------- | -| 01.00.00 | 2025-01-03 | Initial scripts documentation created | - -## Metadata - -- **Document:** scripts/README.md -- **Repository:** https://github.com/mokoconsulting-tech/moko-cassiopeia -- **Version:** 01.00.00 -- **Status:** Active -- 2.49.1