diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e65efd..22697b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,9 +13,60 @@ permissions: contents: read jobs: + formatting-fixes: + name: Run formatting fix scripts + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Run fix_tabs.py if present + shell: bash + run: | + set -euo pipefail + cd "${GITHUB_WORKSPACE}" + if [ -f "scripts/fix_tabs.py" ]; then + echo "Running scripts/fix_tabs.py" + python scripts/fix_tabs.py + else + echo "scripts/fix_tabs.py not found. Skipping." + fi + + - name: Run fix_paths.py if present + shell: bash + run: | + set -euo pipefail + cd "${GITHUB_WORKSPACE}" + if [ -f "scripts/fix_paths.py" ]; then + echo "Running scripts/fix_paths.py" + python scripts/fix_paths.py + else + echo "scripts/fix_paths.py not found. Skipping." + fi + fi + + - name: Fail if formatting scripts modified files + run: | + set -e + if ! git diff --quiet; then + echo "Formatting scripts introduced changes." + echo "Run fix_tabs.py and fix_paths.py locally and commit the results." + git diff + exit 1 + fi + php-lint: name: PHP lint runs-on: ubuntu-latest + needs: formatting-fixes steps: - name: Check out repository @@ -104,7 +155,7 @@ jobs: echo "No manifest validation script found (scripts/validate_manifest.*). Skipping manifest validation step." fi - - name: Run changelog update/verification script if present + - name: Run changelog update or verification script if present run: | set -e echo "Checking for changelog update or verification scripts" diff --git a/CHANGELOG.md b/CHANGELOG.md index 87b5661..2fa7fc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,10 +23,12 @@ ## [TODO] - `./docs/*` - +- Repair `/.github/workflows/build_template.zip.yml` +- `/.github/workflows/build_updatexml.yml` +- Repair `\.github\workflows\ci.yml` +- Repair `\scripts\..` ## [UNRELEASED] -- Placeholder for next release - ## [03.01.00] 2025-12-16 - Created `.github/workflows/` diff --git a/scripts/fix_paths.sh b/scripts/fix_paths.sh new file mode 100644 index 0000000..518490a --- /dev/null +++ b/scripts/fix_paths.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +""" +fix_paths.py + +Normalizes invalid Windows-style backslash separators in repository *paths*. + +What it does +- Uses `git ls-files` as the authoritative inventory of tracked paths. +- Detects any tracked path that contains a backslash (\\). +- Renames the path to a forward-slash (/) equivalent via `git mv`. +- Fails fast on collisions (when the normalized target path already exists). + +What it does NOT do +- Does not rewrite file contents. +- Does not alter untracked files. + +Intended usage +- Called by CI (GitHub Actions) and locally. +- Safe to run repeatedly (idempotent when no invalid paths exist). + +Exit codes +- 0: Success, no invalid paths or all renames completed +- 1: Operational error (git failure, collision, or unexpected exception) +""" + +from __future__ import annotations + +import os +import subprocess +import sys + + +def run(cmd: list[str]) -> subprocess.CompletedProcess: + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + +def ensure_repo_root() -> None: + # In CI we usually start at the repo root, but this enforces determinism. + workspace = os.environ.get("GITHUB_WORKSPACE") + if workspace and os.path.isdir(workspace): + os.chdir(workspace) + + +def require_git_repo() -> None: + p = run(["git", "rev-parse", "--is-inside-work-tree"]) + if p.returncode != 0 or p.stdout.strip() != "true": + print("Error: not inside a git work tree", file=sys.stderr) + sys.exit(1) + + +def list_tracked_paths() -> list[str]: + p = run(["git", "ls-files"]) + if p.returncode != 0: + print("Error: git ls-files failed", file=sys.stderr) + print(p.stderr, file=sys.stderr) + sys.exit(1) + return [line for line in p.stdout.splitlines() if line.strip()] + + +def path_exists(path: str) -> bool: + # Use git to evaluate existence of a tracked path when possible. + # For collision detection we use filesystem existence because the target may not be tracked yet. + return os.path.exists(path) + + +def normalize_path(p: str) -> str: + return p.replace("\\", "/") + + +def git_mv(old: str, new: str) -> None: + p = run(["git", "mv", "-f", old, new]) + if p.returncode != 0: + print(f"Error: git mv failed for {old} -> {new}", file=sys.stderr) + print(p.stderr, file=sys.stderr) + sys.exit(1) + + +def main() -> int: + ensure_repo_root() + require_git_repo() + + tracked = list_tracked_paths() + bad = [p for p in tracked if "\\" in p] + + if not bad: + print("No invalid backslash separators detected in tracked paths") + return 0 + + print(f"Detected {len(bad)} invalid tracked path(s). Normalizing.") + + # Sort longest-first to reduce rename issues in nested scenarios. + bad.sort(key=len, reverse=True) + + for old in bad: + new = normalize_path(old) + + if old == new: + continue + + # Collision check: if target exists and is not the same logical file. + if path_exists(new): + print("Collision detected. Aborting.", file=sys.stderr) + print(f"Source: {old}", file=sys.stderr) + print(f"Target: {new}", file=sys.stderr) + return 1 + + # Ensure destination directory exists. + dest_dir = os.path.dirname(new) + if dest_dir: + os.makedirs(dest_dir, exist_ok=True) + + git_mv(old, new) + print(f"Renamed: {old} -> {new}") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/fix_tabs.h b/scripts/fix_tabs.h new file mode 100644 index 0000000..a3fe533 --- /dev/null +++ b/scripts/fix_tabs.h @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +fix_tabs.py + +Replaces all tab characters (\t) in tracked text files with two spaces. + +Behavior +- Operates only on Git-tracked files. +- Skips binary files automatically via Git attributes. +- Modifies files in place. +- Intended for CI and local formatting enforcement. + +Exit codes +- 0: Success, no errors +- 1: One or more files failed processing +""" + +import subprocess +import sys +from pathlib import Path + +REPLACEMENT = " " # two spaces + + +def get_tracked_files(): + try: + result = subprocess.run( + ["git", "ls-files"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + return [Path(p) for p in result.stdout.splitlines() if p.strip()] + except subprocess.CalledProcessError as e: + print("Error: unable to list git-tracked files", file=sys.stderr) + print(e.stderr, file=sys.stderr) + sys.exit(1) + + +def is_binary(path: Path) -> bool: + try: + with path.open("rb") as f: + chunk = f.read(1024) + return b"\0" in chunk + except Exception: + return True + + +def process_file(path: Path) -> bool: + try: + if is_binary(path): + return True + + content = path.read_text(encoding="utf-8", errors="strict") + + if "\t" not in content: + return True + + updated = content.replace("\t", REPLACEMENT) + path.write_text(updated, encoding="utf-8") + print(f"Normalized tabs: {path}") + return True + except UnicodeDecodeError: + # Non-UTF8 text file, skip safely + return True + except Exception as e: + print(f"Failed processing {path}: {e}", file=sys.stderr) + return False + + +def main() -> int: + failures = False + + for file_path in get_tracked_files(): + if not process_file(file_path): + failures = True + + return 1 if failures else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/verify_changelog.sh b/scripts/verify_changelog.sh index 3884efd..ae0cc20 100644 --- a/scripts/verify_changelog.sh +++ b/scripts/verify_changelog.sh @@ -1,25 +1,71 @@ -#!/bin/bash -set -e + - - https://raw.githubusercontent.com/mokoconsulting-tech/moko-cassiopeia/refs/heads/main/updates.xml - - moko-cassiopeia - 03.01.00 - 2025-09-23 - Jonathan Miller || Moko Consulting - hello@mokoconsulting.tech - (C)GNU General Public License Version 2 - 2025 Moko Consulting - TPL_MOKO-CASSIOPEIA_XML_DESCRIPTION - 1 - - component.php - error.php - index.php - joomla.asset.json - offline.php - templateDetails.xml - html - - - media/templates/site/moko-cassiopeia/css/editor.css - - - js - css - images - fonts - - - topbar - below-topbar - below-logo - menu - search - banner - top-a - top-b - main-top - main-bottom - breadcrumbs - sidebar-left - sidebar-right - bottom-a - bottom-b - footer-menu - footer - debug - offline-header - offline - offline-footer - drawer-left - drawer-right - - - en-GB/tpl_moko-cassiopeia.ini - en-GB/tpl_moko-cassiopeia.sys.ini - en-US/tpl_moko-cassiopeia.ini - en-US/tpl_moko-cassiopeia.sys.ini - - - - -
- - - - - -
+ + https://raw.githubusercontent.com/mokoconsulting-tech/moko-cassiopeia/refs/heads/main/updates.xml + + moko-cassiopeia + 03.01.00 + 2025-09-23 + Jonathan Miller || Moko Consulting + hello@mokoconsulting.tech + (C)GNU General Public License Version 2 - 2025 Moko Consulting + TPL_MOKO-CASSIOPEIA_XML_DESCRIPTION + 1 + + component.php + error.php + index.php + joomla.asset.json + offline.php + templateDetails.xml + html + + + media/templates/site/moko-cassiopeia/css/editor.css + + + js + css + images + fonts + + + topbar + below-topbar + below-logo + menu + search + banner + top-a + top-b + main-top + main-bottom + breadcrumbs + sidebar-left + sidebar-right + bottom-a + bottom-b + footer-menu + footer + debug + offline-header + offline + offline-footer + drawer-left + drawer-right + + + en-GB/tpl_moko-cassiopeia.ini + en-GB/tpl_moko-cassiopeia.sys.ini + en-US/tpl_moko-cassiopeia.ini + en-US/tpl_moko-cassiopeia.sys.ini + + + + +
+ + + + + +
- -
- - - - - - - - - - -
+ +
+ + + + + + + + + + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
+ +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - -
- - + + + + + + + + + + + + +
+
+