From ac7c354856e07a2b6b1e0d2c2d135ad88fd6ef09 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 23:38:28 +0000 Subject: [PATCH 1/5] Initial plan From d09cf9c65e4be17c9235c3ba65415fa3f067b539 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:07:58 +0000 Subject: [PATCH 2/5] Add version hierarchy validation to prevent duplicate versions across branch prefixes Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- .github/workflows/version_branch.yml | 94 +++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/.github/workflows/version_branch.yml b/.github/workflows/version_branch.yml index 0f4fb4e..779465e 100644 --- a/.github/workflows/version_branch.yml +++ b/.github/workflows/version_branch.yml @@ -28,6 +28,15 @@ on: options: - "true" - "false" + branch_prefix: + description: "Branch prefix for version (version/ for stable, rc/ for release candidate, dev/ for development)" + required: false + default: "dev/" + type: choice + options: + - "dev/" + - "rc/" + - "version/" concurrency: group: ${{ github.workflow }}-${{ github.repository }}-${{ github.event.inputs.new_version }} @@ -51,7 +60,7 @@ jobs: REPORT_ONLY: ${{ github.event.inputs.report_only }} COMMIT_CHANGES: ${{ github.event.inputs.commit_changes }} BASE_BRANCH: ${{ github.ref_name }} - BRANCH_PREFIX: 'dev/' + BRANCH_PREFIX: ${{ github.event.inputs.branch_prefix || 'dev/' }} ERROR_LOG: /tmp/version_branch_errors.log CI_HELPERS: /tmp/moko_ci_helpers.sh REPORT_PATH: /tmp/version-bump-report.json @@ -124,10 +133,16 @@ jobs: [[ -n "${NEW_VERSION}" ]] || { echo "[ERROR] new_version missing" >&2; exit 2; } [[ "${NEW_VERSION}" =~ ^[0-9]{2}[.][0-9]{2}[.][0-9]{2}$ ]] || { echo "[ERROR] Invalid version format: ${NEW_VERSION}" >&2; exit 2; } - if [[ "${BRANCH_PREFIX}" != "dev/" ]]; then - echo "[FATAL] BRANCH_PREFIX is locked by policy. Expected 'dev/' but got '${BRANCH_PREFIX}'." >&2 - exit 2 - fi + # Validate BRANCH_PREFIX is one of the allowed values + case "${BRANCH_PREFIX}" in + "dev/"|"rc/"|"version/") + echo "[INFO] ✓ Branch prefix '${BRANCH_PREFIX}' is valid" + ;; + *) + echo "[FATAL] BRANCH_PREFIX must be one of: dev/, rc/, version/ (got: '${BRANCH_PREFIX}')" >&2 + exit 2 + ;; + esac if ! moko_bool "${REPORT_ONLY}" && [[ "${COMMIT_CHANGES}" != "true" ]]; then echo "[FATAL] commit_changes must be 'true' when report_only is 'false' to ensure the branch is auditable." >&2 @@ -230,11 +245,13 @@ jobs: source "$CI_HELPERS" moko_init "Branch namespace collision defense" - # Skip collision check for the static 'dev/' prefix - if [[ "${BRANCH_PREFIX}" == "dev/" ]]; then - echo "[INFO] Skipping collision check for static prefix 'dev/'" >&2 - exit 0 - fi + # Skip collision check for known static prefixes + case "${BRANCH_PREFIX}" in + "dev/"|"rc/"|"version/") + echo "[INFO] Skipping collision check for known prefix '${BRANCH_PREFIX}'" >&2 + exit 0 + ;; + esac PREFIX_TOP="${BRANCH_PREFIX%%/*}" if git ls-remote --exit-code --heads origin "${PREFIX_TOP}" >/dev/null 2>&1; then @@ -270,6 +287,63 @@ jobs: git checkout -B "${BRANCH_NAME}" "origin/${BASE_BRANCH}" echo "BRANCH_NAME=${BRANCH_NAME}" >> "$GITHUB_ENV" + - name: Validate version hierarchy (prevent creating in lower priority branches) + if: ${{ env.REPORT_ONLY != 'true' }} + run: | + source "$CI_HELPERS" + moko_init "Validate version hierarchy" + + # Version hierarchy (highest to lowest priority): + # 1. version/X.Y.Z - production/stable versions + # 2. rc/X.Y.Z - release candidate versions + # 3. dev/X.Y.Z - development versions + # + # Rule: If a version exists in a higher priority branch, + # do not allow creating it in a lower priority branch + + BRANCH_NAME="${BRANCH_PREFIX}${NEW_VERSION}" + + echo "[INFO] Checking version hierarchy for: ${NEW_VERSION}" + echo "[INFO] Current branch prefix: ${BRANCH_PREFIX}" + + # Determine current priority level + case "${BRANCH_PREFIX}" in + "version/") + echo "[INFO] Creating stable version branch - no hierarchy checks needed" + ;; + "rc/") + echo "[INFO] Creating RC branch - checking if version exists in stable" + if git ls-remote --exit-code --heads origin "version/${NEW_VERSION}" >/dev/null 2>&1; then + echo "[FATAL] Version ${NEW_VERSION} already exists in stable branch: version/${NEW_VERSION}" >&2 + echo "[FATAL] Cannot create RC version for a version that already exists in stable" >&2 + exit 2 + fi + echo "[INFO] ✓ No conflict with stable versions" + ;; + "dev/") + echo "[INFO] Creating dev branch - checking if version exists in stable or RC" + + # Check stable versions + if git ls-remote --exit-code --heads origin "version/${NEW_VERSION}" >/dev/null 2>&1; then + echo "[FATAL] Version ${NEW_VERSION} already exists in stable branch: version/${NEW_VERSION}" >&2 + echo "[FATAL] Cannot create dev version for a version that already exists in stable" >&2 + exit 2 + fi + + # Check RC versions + if git ls-remote --exit-code --heads origin "rc/${NEW_VERSION}" >/dev/null 2>&1; then + echo "[FATAL] Version ${NEW_VERSION} already exists in RC branch: rc/${NEW_VERSION}" >&2 + echo "[FATAL] Cannot create dev version for a version that already exists in RC" >&2 + exit 2 + fi + + echo "[INFO] ✓ No conflict with stable or RC versions" + ;; + *) + echo "[WARN] Unknown branch prefix: ${BRANCH_PREFIX}, skipping hierarchy check" + ;; + esac + - name: Enforce update feed files absent (update.xml, updates.xml) if: ${{ env.REPORT_ONLY != 'true' }} run: | From de6b40413da52be1197145529fc60f93dbb95510 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:09:30 +0000 Subject: [PATCH 3/5] Remove unused BRANCH_NAME variable from validation step Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- .github/workflows/version_branch.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/version_branch.yml b/.github/workflows/version_branch.yml index 779465e..cb55333 100644 --- a/.github/workflows/version_branch.yml +++ b/.github/workflows/version_branch.yml @@ -300,8 +300,6 @@ jobs: # # Rule: If a version exists in a higher priority branch, # do not allow creating it in a lower priority branch - - BRANCH_NAME="${BRANCH_PREFIX}${NEW_VERSION}" echo "[INFO] Checking version hierarchy for: ${NEW_VERSION}" echo "[INFO] Current branch prefix: ${BRANCH_PREFIX}" From d89744df286b9b7bedfd59fe12ba50b559c5dcba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:24:38 +0000 Subject: [PATCH 4/5] Add CI improvements and version management scripts Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- .github/workflows/ci.yml | 2 + scripts/README.md | 81 +++++++++++ scripts/run/check_version.sh | 191 ++++++++++++++++++++++++++ scripts/run/list_versions.sh | 127 +++++++++++++++++ scripts/validate/version_hierarchy.sh | 134 ++++++++++++++++++ scripts/validate/workflows.sh | 186 +++++++++++++++++++++++++ 6 files changed, 721 insertions(+) create mode 100755 scripts/run/check_version.sh create mode 100755 scripts/run/list_versions.sh create mode 100755 scripts/validate/version_hierarchy.sh create mode 100755 scripts/validate/workflows.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4cfa73..b424006 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: scripts/validate/manifest.sh scripts/validate/xml_wellformed.sh + scripts/validate/workflows.sh - name: Optional validations run: | @@ -59,6 +60,7 @@ jobs: scripts/validate/php_syntax.sh scripts/validate/tabs.sh scripts/validate/version_alignment.sh + scripts/validate/version_hierarchy.sh - name: CI summary if: always() diff --git a/scripts/README.md b/scripts/README.md index 5323fb6..d90c4a4 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -121,6 +121,28 @@ 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. @@ -279,6 +301,65 @@ Example output: [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 diff --git a/scripts/run/check_version.sh b/scripts/run/check_version.sh new file mode 100755 index 0000000..910fae6 --- /dev/null +++ b/scripts/run/check_version.sh @@ -0,0 +1,191 @@ +#!/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 ! printf '%s' "$v" | grep -Eq '^[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 new file mode 100755 index 0000000..ebf22d6 --- /dev/null +++ b/scripts/run/list_versions.sh @@ -0,0 +1,127 @@ +#!/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/validate/version_hierarchy.sh b/scripts/validate/version_hierarchy.sh new file mode 100755 index 0000000..e692735 --- /dev/null +++ b/scripts/validate/version_hierarchy.sh @@ -0,0 +1,134 @@ +#!/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 new file mode 100755 index 0000000..a8a147d --- /dev/null +++ b/scripts/validate/workflows.sh @@ -0,0 +1,186 @@ +#!/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 +import yaml + +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 From 2534fd3cae53a91b64a8a690ec6e4204f749b6ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:26:21 +0000 Subject: [PATCH 5/5] Address code review feedback - improve error handling and use bash built-ins Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- scripts/run/check_version.sh | 2 +- scripts/validate/workflows.sh | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/run/check_version.sh b/scripts/run/check_version.sh index 910fae6..d36b114 100755 --- a/scripts/run/check_version.sh +++ b/scripts/run/check_version.sh @@ -85,7 +85,7 @@ validate_prefix() { validate_version() { local v="$1" - if ! printf '%s' "$v" | grep -Eq '^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$'; then + if [[ ! "$v" =~ ^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$ ]]; then die "Invalid version format: $v (expected NN.NN.NN)" fi } diff --git a/scripts/validate/workflows.sh b/scripts/validate/workflows.sh index a8a147d..aad70d9 100755 --- a/scripts/validate/workflows.sh +++ b/scripts/validate/workflows.sh @@ -52,7 +52,12 @@ validate_yaml_syntax() { python3 - "$file" <<'PYEOF' import sys -import yaml + +try: + import yaml +except ModuleNotFoundError: + print("WARNING: PyYAML module not installed. Install with: pip3 install pyyaml") + sys.exit(0) file_path = sys.argv[1]