Delete all shell script files, keeping only Python versions

Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-04 08:21:25 +00:00
parent c471225a93
commit 09989a386c
29 changed files with 0 additions and 4440 deletions

View File

@@ -1,208 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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})"

View File

@@ -1,119 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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"

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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

View File

@@ -1,247 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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 <extension ...>
if [ "${#manifest_candidates[@]}" -eq 0 ]; then
while IFS= read -r f; do
[ -n "${f}" ] && manifest_candidates+=("${f}")
done < <(grep -Rsl --include='*.xml' '<extension' "${SRC_DIR}" 2>/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 <extension> 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 <extension>", 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 <file> 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})"

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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"

View File

@@ -1,78 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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"

View File

@@ -1,113 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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 <filename>' 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"

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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"

View File

@@ -1,100 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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

View File

@@ -1,134 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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

View File

@@ -1,191 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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

View File

@@ -1,91 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (./LICENSE.md).
# ============================================================================
# ============================================================================
# FILE INFORMATION
# ============================================================================
# DEFGROUP: Script.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"