diff --git a/.gitignore b/.gitignore index 391f47d..44a63a2 100644 --- a/.gitignore +++ b/.gitignore @@ -113,7 +113,7 @@ releases/ build/ dist/ out/ -site/ +/site/ *.map *.css.map *.js.map diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml deleted file mode 100644 index 23beccb..0000000 --- a/.mokogitea/manifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Template-Joomla - MokoConsulting - Template repository for Joomla extensions (plugins, modules, components, templates) - GNU General Public License v3 - - - joomla - 05.00.00 - https://git.mokoconsulting.tech/MokoConsulting/moko-platform - - - PHP - joomla-extension - src/ - - diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 5865324..4489ae0 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -7,7 +7,7 @@ # INGROUP: mokocli.Release # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli # PATH: /templates/workflows/universal/auto-release.yml.template -# VERSION: 05.00.00 +# VERSION: 05.01.00 # BRIEF: Universal build & release � detects platform from manifest.xml # # +=======================================================================+ @@ -75,6 +75,7 @@ jobs: with: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 + submodules: recursive - name: Setup mokocli tools env: @@ -173,6 +174,7 @@ jobs: with: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 0 + submodules: recursive - name: Configure git for bot pushes run: | diff --git a/.mokogitea/workflows/ci-joomla.yml b/.mokogitea/workflows/ci-joomla.yml index 0c6f5ea..9d5a1c9 100644 --- a/.mokogitea/workflows/ci-joomla.yml +++ b/.mokogitea/workflows/ci-joomla.yml @@ -45,22 +45,22 @@ jobs: fi php -v && composer --version - - name: Setup moko-platform tools + - name: Setup mokocli tools env: - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} + MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }} + MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} run: | - if [ -d "/tmp/moko-platform" ] || [ -d "/opt/moko-platform" ]; then - echo "moko-platform already available on runner — skipping clone" + if [ -d "/opt/mokocli" ] || [ -d "/tmp/mokocli" ]; then + echo "mokocli already available on runner — skipping clone" else git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ - /tmp/moko-platform 2>/dev/null || echo "moko-platform clone skipped — continuing without it" + "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git" \ + /tmp/mokocli 2>/dev/null || echo "mokocli clone skipped — continuing without it" fi - name: Install dependencies env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}' + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}"}}' run: | if [ -f "composer.json" ]; then composer install \ @@ -164,6 +164,75 @@ jobs: echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY fi + - name: Update server & packaging checks + continue-on-error: true + run: | + echo "### Update Server & Packaging" >> $GITHUB_STEP_SUMMARY + WARNINGS=0 + + # Find the extension manifest + MANIFEST="" + for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY + else + EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) + + # 1. Check exists and uses MokoGitea update server + if ! grep -q '' "$MANIFEST" 2>/dev/null; then + echo "::warning file=${MANIFEST}::Missing \`\` tag — extension will not receive OTA updates" + echo "- **Missing** \`\` — extension will not receive OTA updates" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + else + SERVER_URL=$(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1) + if [ -z "$SERVER_URL" ]; then + echo "::warning file=${MANIFEST}::\`\` is empty — no server URL defined" + echo "- **Empty** \`\` — no server URL defined" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + elif ! echo "$SERVER_URL" | grep -q 'git\.mokoconsulting\.tech'; then + echo "::warning file=${MANIFEST}::Update server does not use MokoGitea engine: ${SERVER_URL}" + echo "- **Non-MokoGitea update server:** \`${SERVER_URL}\`" >> $GITHUB_STEP_SUMMARY + echo " Expected: \`https://git.mokoconsulting.tech/{org}/{repo}/updates.xml\`" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + else + echo "- \`\`: MokoGitea engine ✓" >> $GITHUB_STEP_SUMMARY + fi + fi + + # 2. Check tag exists + if ! grep -q '/dev/null; then + echo "::warning file=${MANIFEST}::Missing \`\` tag — download ID authentication is not configured" + echo "- **Missing** \`\` — download ID authentication not configured" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + else + echo "- \`\`: present ✓" >> $GITHUB_STEP_SUMMARY + fi + + # 3. For packages: check tag + if [ "$EXT_TYPE" = "package" ]; then + if ! grep -q '' "$MANIFEST" 2>/dev/null; then + echo "::warning file=${MANIFEST}::Package is missing \`\` — child extensions will not be removed on uninstall" + echo "- **Missing** \`\` — child extensions will remain when package is uninstalled" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + else + echo "- \`\`: present ✓" >> $GITHUB_STEP_SUMMARY + fi + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$WARNINGS" -gt 0 ]; then + echo "**${WARNINGS} packaging warning(s).** These won't block CI but should be addressed." >> $GITHUB_STEP_SUMMARY + else + echo "**Update server & packaging checks passed.**" >> $GITHUB_STEP_SUMMARY + fi + - name: Check language files referenced in manifest run: | echo "### Language File Check" >> $GITHUB_STEP_SUMMARY @@ -245,10 +314,676 @@ jobs: echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY fi + - name: Check config.xml and access.xml for components + run: | + echo "### Component Config & ACL Check" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + # Find all component manifests (XML with type="component") + # Uses maxdepth 10 to reach into nested package repos (packages/*/source/packages/com_*/...) + COMP_MANIFESTS=$(find . -maxdepth 10 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./.claude/*" -exec grep -l ']*type="component"' {} \; 2>/dev/null || true) + + if [ -z "$COMP_MANIFESTS" ]; then + echo "No component extensions found — skipping." >> $GITHUB_STEP_SUMMARY + else + for MANIFEST in $COMP_MANIFESTS; do + COMP_DIR=$(dirname "$MANIFEST") + COMP_NAME=$(basename "$COMP_DIR") + echo "Component: `${COMP_NAME}` (manifest: `${MANIFEST}`)" >> $GITHUB_STEP_SUMMARY + + # Check access.xml exists + ACCESS_FILE=$(find "$COMP_DIR" -name "access.xml" -not -path "./.git/*" 2>/dev/null | head -1) + if [ -z "$ACCESS_FILE" ]; then + echo "- Missing `access.xml` — ACL permissions will not work." >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + if command -v php &> /dev/null; then + if ! php -r "@simplexml_load_file('$ACCESS_FILE') ?: exit(1);" 2>/dev/null; then + echo "- `access.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + for ACTION in core.admin core.manage; do + if ! grep -q "name=\"${ACTION}\"" "$ACCESS_FILE" 2>/dev/null; then + echo "- `access.xml` missing required action: `${ACTION}`" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done + echo "- `access.xml`: valid" >> $GITHUB_STEP_SUMMARY + fi + fi + fi + + # Check config.xml exists + CONFIG_FILE=$(find "$COMP_DIR" -name "config.xml" -not -path "./.git/*" 2>/dev/null | head -1) + if [ -z "$CONFIG_FILE" ]; then + echo "- Missing `config.xml` — component Options page will be empty." >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + if command -v php &> /dev/null; then + if ! php -r "@simplexml_load_file('$CONFIG_FILE') ?: exit(1);" 2>/dev/null; then + echo "- `config.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + echo "- `config.xml`: valid" >> $GITHUB_STEP_SUMMARY + fi + fi + fi + done + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} config/ACL issue(s) found.**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**Component config & ACL check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: SQL schema validation + run: | + echo "### SQL Schema Validation" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + # Find SQL files in source/htdocs + SQL_FILES=$(find . -name "*.sql" -path "*/sql/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) + if [ -z "$SQL_FILES" ]; then + echo "No SQL files found — skipping." >> $GITHUB_STEP_SUMMARY + else + echo "Found $(echo "$SQL_FILES" | wc -l) SQL file(s)" >> $GITHUB_STEP_SUMMARY + + for FILE in $SQL_FILES; do + # Basic syntax check: balanced parentheses, no empty files + SIZE=$(wc -c < "$FILE" | tr -d ' ') + if [ "$SIZE" -eq 0 ]; then + echo "- Empty SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + continue + fi + + # Check for common SQL errors + if grep -qP '^\s*$' "$FILE" && [ "$SIZE" -lt 5 ]; then + echo "- Whitespace-only SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + continue + fi + + echo "- \`${FILE}\`: ${SIZE} bytes" >> $GITHUB_STEP_SUMMARY + done + + # Check update SQL files follow version numbering pattern + UPDATE_DIR=$(find . -path "*/sql/updates/mysql" -type d -not -path "./.git/*" 2>/dev/null | head -1) + if [ -n "$UPDATE_DIR" ]; then + BAD_NAMES=0 + for UFILE in "$UPDATE_DIR"/*.sql; do + [ ! -f "$UFILE" ] && continue + BASENAME=$(basename "$UFILE" .sql) + if ! echo "$BASENAME" | grep -qP '^\d+\.\d+\.\d+'; then + echo "- Update file \`${UFILE}\` does not follow version naming (expected X.Y.Z.sql)" >> $GITHUB_STEP_SUMMARY + BAD_NAMES=$((BAD_NAMES + 1)) + fi + done + if [ "$BAD_NAMES" -gt 0 ]; then + ERRORS=$((ERRORS + BAD_NAMES)) + fi + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} SQL issue(s) found.**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**SQL schema validation passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Manifest file references check + run: | + echo "### Manifest File References" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + MANIFEST="" + for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY + else + MANIFEST_DIR=$(dirname "$MANIFEST") + + # Check references + FILENAMES=$(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null || true) + for F in $FILENAMES; do + if [ ! -f "${MANIFEST_DIR}/${F}" ] && [ ! -d "${MANIFEST_DIR}/${F}" ]; then + echo "- Missing: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done + + # Check references + FOLDERS=$(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null || true) + for F in $FOLDERS; do + if [ ! -d "${MANIFEST_DIR}/${F}" ]; then + echo "- Missing folder: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done + + # Check references in package manifests (ZIP files won't exist in source) + EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) + if [ "$EXT_TYPE" != "package" ]; then + FILES=$(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null || true) + for F in $FILES; do + if [ ! -f "${MANIFEST_DIR}/${F}" ]; then + echo "- Missing file: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} missing file reference(s).**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**Manifest file references check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Form XML validation + run: | + echo "### Form XML Validation" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + FORM_FILES=$(find . -name "*.xml" -path "*/forms/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) + if [ -z "$FORM_FILES" ]; then + echo "No form XML files found — skipping." >> $GITHUB_STEP_SUMMARY + else + echo "Found $(echo "$FORM_FILES" | wc -l) form file(s)" >> $GITHUB_STEP_SUMMARY + for FILE in $FORM_FILES; do + if command -v php &> /dev/null; then + if ! php -r "@simplexml_load_file('$FILE') ?: exit(1);" 2>/dev/null; then + echo "- \`${FILE}\`: malformed XML" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + # Check for valid Joomla form structure + if ! grep -qE '/dev/null; then + echo "- \`${FILE}\`: no \`
\`, \`\`, or \`
\` elements found" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + echo "- \`${FILE}\`: valid" >> $GITHUB_STEP_SUMMARY + fi + fi + fi + done + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} form XML issue(s).**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**Form XML validation passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Deprecated Joomla API check + continue-on-error: true + run: | + echo "### Deprecated Joomla API Check" >> $GITHUB_STEP_SUMMARY + WARNINGS=0 + + SRC_DIR="" + for DIR in source/ src/ htdocs/; do + [ -d "$DIR" ] && SRC_DIR="$DIR" && break + done + + if [ -z "$SRC_DIR" ]; then + echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY + else + # Joomla 3/4 deprecated patterns that break in Joomla 6 + PATTERNS=( + 'JFactory::' + 'JText::' + 'JHtml::' + 'JRoute::' + 'JUri::' + 'JLog::' + 'JTable::' + 'JInput' + 'CMSFactory::\$application' + 'JApplicationCms' + ) + + for PATTERN in "${PATTERNS[@]}"; do + HITS=$(grep -rnl "$PATTERN" "$SRC_DIR" --include="*.php" 2>/dev/null || true) + if [ -n "$HITS" ]; then + COUNT=$(echo "$HITS" | wc -l) + echo "- \`${PATTERN}\` found in ${COUNT} file(s)" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + COUNT)) + fi + done + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$WARNINGS" -gt 0 ]; then + echo "**${WARNINGS} deprecated API usage(s) found.** These will break in Joomla 6." >> $GITHUB_STEP_SUMMARY + else + echo "**No deprecated APIs found.**" >> $GITHUB_STEP_SUMMARY + fi + fi + + - name: Template output escaping check + continue-on-error: true + run: | + echo "### Template Output Escaping" >> $GITHUB_STEP_SUMMARY + WARNINGS=0 + + TMPL_FILES=$(find . -name "*.php" -path "*/tmpl/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) + if [ -z "$TMPL_FILES" ]; then + echo "No template files found — skipping." >> $GITHUB_STEP_SUMMARY + else + echo "Found $(echo "$TMPL_FILES" | wc -l) template file(s)" >> $GITHUB_STEP_SUMMARY + + for FILE in $TMPL_FILES; do + # Check for unescaped output: or echo $var without escape() + UNESCAPED=$(grep -nP '<\?=\s*\$(?!this->escape)' "$FILE" 2>/dev/null || true) + if [ -n "$UNESCAPED" ]; then + HITS=$(echo "$UNESCAPED" | wc -l) + echo "- \`${FILE}\`: ${HITS} unescaped \`\` output(s) — use \`escape(\$var) ?>\`" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + HITS)) + fi + + # Check for echo without escaping in template context + RAW_ECHO=$(grep -nP '^\s*echo\s+\$(?!this->escape)' "$FILE" 2>/dev/null || true) + if [ -n "$RAW_ECHO" ]; then + HITS=$(echo "$RAW_ECHO" | wc -l) + echo "- \`${FILE}\`: ${HITS} raw \`echo \$var\` — consider \`echo \$this->escape(\$var)\`" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + HITS)) + fi + done + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$WARNINGS" -gt 0 ]; then + echo "**${WARNINGS} potential XSS risk(s) in templates.** Review unescaped output." >> $GITHUB_STEP_SUMMARY + else + echo "**All template output appears properly escaped.**" >> $GITHUB_STEP_SUMMARY + fi + fi + + - name: Namespace consistency check + run: | + echo "### Namespace Consistency" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + # Find component/plugin manifests with tags + MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '/dev/null || true) + + if [ -z "$MANIFESTS" ]; then + echo "No manifests with \`\` found — skipping." >> $GITHUB_STEP_SUMMARY + else + for MANIFEST in $MANIFESTS; do + NS_PATH=$(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1) + [ -z "$NS_PATH" ] && continue + MANIFEST_DIR=$(dirname "$MANIFEST") + + echo "Manifest: \`${MANIFEST}\` → namespace \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY + + # Check PHP files have matching namespace + while IFS= read -r -d '' PHP_FILE; do + FILE_NS=$(grep -oP '^\s*namespace\s+\K[^;]+' "$PHP_FILE" 2>/dev/null | head -1) + [ -z "$FILE_NS" ] && continue + + # Namespace should start with the manifest namespace path + if ! echo "$FILE_NS" | grep -qF "${NS_PATH}"; then + echo "- \`${PHP_FILE}\`: namespace \`${FILE_NS}\` doesn't match manifest \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done < <(find "$MANIFEST_DIR" -name "*.php" -path "*/src/*" -not -path "./vendor/*" -print0 2>/dev/null) + done + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} namespace mismatch(es).**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**Namespace consistency check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: SPDX license header check + continue-on-error: true + run: | + echo "### SPDX License Headers" >> $GITHUB_STEP_SUMMARY + MISSING=0 + + SRC_DIR="" + for DIR in source/ src/ htdocs/; do + [ -d "$DIR" ] && SRC_DIR="$DIR" && break + done + + if [ -z "$SRC_DIR" ]; then + echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY + else + TOTAL=0 + while IFS= read -r -d '' FILE; do + TOTAL=$((TOTAL + 1)) + if ! head -10 "$FILE" | grep -qi "SPDX"; then + echo "- Missing SPDX header: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY + MISSING=$((MISSING + 1)) + fi + done < <(find "$SRC_DIR" -name "*.php" -not -path "./vendor/*" -print0) + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$MISSING" -gt 0 ]; then + echo "**${MISSING}/${TOTAL} PHP file(s) missing SPDX license header.**" >> $GITHUB_STEP_SUMMARY + else + echo "**All ${TOTAL} PHP files have SPDX headers.**" >> $GITHUB_STEP_SUMMARY + fi + fi + + - name: Service provider check + run: | + echo "### Service Provider Check" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + PROVIDERS=$(find . -name "provider.php" -path "*/services/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) + if [ -z "$PROVIDERS" ]; then + echo "No service providers found — skipping." >> $GITHUB_STEP_SUMMARY + else + for FILE in $PROVIDERS; do + # Must return a ServiceProviderInterface + if ! grep -qP 'ServiceProviderInterface|ComponentInterface|MVCFactoryInterface|DispatcherInterface' "$FILE" 2>/dev/null; then + echo "- \`${FILE}\`: does not reference ServiceProviderInterface or component interfaces" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + echo "- \`${FILE}\`: valid service provider" >> $GITHUB_STEP_SUMMARY + fi + + # Must have return statement + if ! grep -qP '^\s*return\s+new\s+' "$FILE" 2>/dev/null; then + echo "- \`${FILE}\`: missing \`return new ...\` statement" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} service provider issue(s).**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**Service provider check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Script file reference check + run: | + echo "### Script File Reference" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + MANIFEST="" + for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY + else + MANIFEST_DIR=$(dirname "$MANIFEST") + SCRIPT_FILE=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1) + if [ -z "$SCRIPT_FILE" ]; then + echo "No \`\` referenced — skipping." >> $GITHUB_STEP_SUMMARY + elif [ ! -f "${MANIFEST_DIR}/${SCRIPT_FILE}" ]; then + echo "::error file=${MANIFEST}::Manifest references \`${SCRIPT_FILE}\` but file does not exist" + echo "- **Missing** \`${SCRIPT_FILE}\` — referenced in \`\` but not found" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + echo "- \`${SCRIPT_FILE}\`: present ✓" >> $GITHUB_STEP_SUMMARY + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} script file issue(s).**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**Script file reference check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Media folder validation + run: | + echo "### Media Folder Validation" >> $GITHUB_STEP_SUMMARY + ERRORS=0 + + MANIFEST="" + for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY + else + MANIFEST_DIR=$(dirname "$MANIFEST") + + # Check tag and its folder/filename children + MEDIA_DEST=$(grep -oP ']*\bdestination="\K[^"]+' "$MANIFEST" 2>/dev/null | head -1) + MEDIA_FOLDER=$(grep -oP ']*\bfolder="\K[^"]+' "$MANIFEST" 2>/dev/null | head -1) + + if [ -z "$MEDIA_DEST" ] && [ -z "$MEDIA_FOLDER" ]; then + echo "No \`\` tag found — skipping." >> $GITHUB_STEP_SUMMARY + else + if [ -n "$MEDIA_FOLDER" ] && [ ! -d "${MANIFEST_DIR}/${MEDIA_FOLDER}" ]; then + echo "::error file=${MANIFEST}::\`\` references missing directory" + echo "- **Missing** media folder \`${MEDIA_FOLDER}\`" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + echo "- Media folder \`${MEDIA_FOLDER:-(inline)}\`: present ✓" >> $GITHUB_STEP_SUMMARY + + # Check child references inside block + if [ -n "$MEDIA_FOLDER" ]; then + MEDIA_FOLDERS=$(sed -n '//p' "$MANIFEST" | grep -oP '\K[^<]+' 2>/dev/null || true) + for F in $MEDIA_FOLDERS; do + if [ ! -d "${MANIFEST_DIR}/${MEDIA_FOLDER}/${F}" ]; then + echo "- **Missing** media subfolder \`${MEDIA_FOLDER}/${F}\`" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done + + MEDIA_FILES=$(sed -n '//p' "$MANIFEST" | grep -oP '\K[^<]+' 2>/dev/null || true) + for F in $MEDIA_FILES; do + if [ ! -f "${MANIFEST_DIR}/${MEDIA_FOLDER}/${F}" ]; then + echo "- **Missing** media file \`${MEDIA_FOLDER}/${F}\`" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + fi + done + fi + fi + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${ERRORS}" -gt 0 ]; then + echo "**${ERRORS} media reference issue(s).**" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "**Media folder validation passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Target platform check + continue-on-error: true + run: | + echo "### Target Platform Check" >> $GITHUB_STEP_SUMMARY + WARNINGS=0 + + MANIFEST="" + for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY + else + # Check updates.xml for targetplatform if it exists + if [ -f "updates.xml" ]; then + if ! grep -q '/dev/null; then + echo "::warning file=updates.xml::No \`\` found — Joomla updater cannot filter by compatible version" + echo "- **Missing** \`\` in updates.xml" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + else + echo "- \`\` in updates.xml: present ✓" >> $GITHUB_STEP_SUMMARY + fi + fi + + # Check manifest for minimum PHP/Joomla version hints + if ! grep -qP '|targetplatform|joomla.*version' "$MANIFEST" 2>/dev/null; then + echo "::warning file=${MANIFEST}::No minimum Joomla or PHP version constraint found in manifest" + echo "- **Missing** version constraints (\`\` or \`\`)" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + else + echo "- Version constraints in manifest: present ✓" >> $GITHUB_STEP_SUMMARY + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$WARNINGS" -gt 0 ]; then + echo "**${WARNINGS} target platform warning(s).**" >> $GITHUB_STEP_SUMMARY + else + echo "**Target platform check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Changelog URL check + continue-on-error: true + run: | + echo "### Changelog URL Check" >> $GITHUB_STEP_SUMMARY + WARNINGS=0 + + MANIFEST="" + for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY + else + if ! grep -q '' "$MANIFEST" 2>/dev/null; then + echo "::warning file=${MANIFEST}::Missing \`\` — Joomla updater will not display changelogs" + echo "- **Missing** \`\` — Joomla 4+ shows changelogs in the update manager when this is set" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + else + CHANGELOG_URL=$(grep -oP '\K[^<]+' "$MANIFEST" | head -1) + echo "- \`\`: \`${CHANGELOG_URL}\` ✓" >> $GITHUB_STEP_SUMMARY + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$WARNINGS" -gt 0 ]; then + echo "**${WARNINGS} changelog URL warning(s).**" >> $GITHUB_STEP_SUMMARY + else + echo "**Changelog URL check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Duplicate file references check + continue-on-error: true + run: | + echo "### Duplicate File References" >> $GITHUB_STEP_SUMMARY + WARNINGS=0 + + MANIFEST="" + for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY + else + # Extract all and references + ALL_REFS=$(grep -oP '<(filename|folder)[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null | sort || true) + if [ -z "$ALL_REFS" ]; then + echo "No file/folder references found — skipping." >> $GITHUB_STEP_SUMMARY + else + DUPES=$(echo "$ALL_REFS" | uniq -d) + if [ -n "$DUPES" ]; then + while IFS= read -r DUP; do + COUNT=$(echo "$ALL_REFS" | grep -cx "$DUP") + echo "::warning file=${MANIFEST}::Duplicate reference: \`${DUP}\` appears ${COUNT} times (may be valid if in different sections)" + echo "- **Duplicate:** \`${DUP}\` (${COUNT}x) — check if cross-section" >> $GITHUB_STEP_SUMMARY + WARNINGS=$((WARNINGS + 1)) + done <<< "$DUPES" + else + TOTAL=$(echo "$ALL_REFS" | wc -l) + echo "All ${TOTAL} file/folder references are unique." >> $GITHUB_STEP_SUMMARY + fi + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$WARNINGS" -gt 0 ]; then + echo "**${WARNINGS} duplicate reference(s) found.** Review for cross-section validity." >> $GITHUB_STEP_SUMMARY + else + echo "**Duplicate file references check passed.**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Empty language keys check + continue-on-error: true + run: | + echo "### Empty Language Keys" >> $GITHUB_STEP_SUMMARY + WARNINGS=0 + + LANG_FILES=$(find . -name "*.ini" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) + if [ -z "$LANG_FILES" ]; then + echo "No .ini language files found — skipping." >> $GITHUB_STEP_SUMMARY + else + TOTAL_FILES=0 + for FILE in $LANG_FILES; do + TOTAL_FILES=$((TOTAL_FILES + 1)) + # Find lines with KEY= but no value (empty or whitespace-only after =) + EMPTY_KEYS=$(grep -nP '^[A-Z_]+=\s*$' "$FILE" 2>/dev/null || true) + if [ -n "$EMPTY_KEYS" ]; then + COUNT=$(echo "$EMPTY_KEYS" | wc -l) + echo "::warning file=${FILE}::${COUNT} empty language key(s)" + echo "- \`${FILE}\`: ${COUNT} empty key(s)" >> $GITHUB_STEP_SUMMARY + while IFS= read -r LINE; do + LINE_NUM=$(echo "$LINE" | cut -d: -f1) + KEY=$(echo "$LINE" | cut -d: -f2 | cut -d= -f1) + echo " - Line ${LINE_NUM}: \`${KEY}\`" >> $GITHUB_STEP_SUMMARY + done <<< "$EMPTY_KEYS" + WARNINGS=$((WARNINGS + COUNT)) + fi + done + + if [ "$WARNINGS" -eq 0 ]; then + echo "All ${TOTAL_FILES} language file(s) have populated keys." >> $GITHUB_STEP_SUMMARY + fi + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$WARNINGS" -gt 0 ]; then + echo "**${WARNINGS} empty language key(s) across ${TOTAL_FILES} file(s).**" >> $GITHUB_STEP_SUMMARY + else + echo "**Empty language keys check passed.**" >> $GITHUB_STEP_SUMMARY + fi + release-readiness: name: Release Readiness Check runs-on: ubuntu-latest if: github.event_name == 'pull_request' && github.base_ref == 'main' + continue-on-error: true steps: - name: Checkout repository @@ -370,7 +1105,7 @@ jobs: - name: Install dependencies env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}' + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}"}}' run: | if [ -f "composer.json" ]; then composer install \ @@ -420,7 +1155,7 @@ jobs: - name: Install dependencies env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}' + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}"}}' run: | if [ -f "composer.json" ]; then composer install --no-interaction --prefer-dist --optimize-autoloader @@ -487,13 +1222,13 @@ jobs: steps: - name: Trigger pre-release build env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} REPO: ${{ github.repository }} BRANCH: ${{ github.head_ref }} run: | curl -s -X POST \ "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" \ - -H "Authorization: token ${GA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/custom/workflow-sync-trigger.yml b/.mokogitea/workflows/custom/workflow-sync-trigger.yml new file mode 100644 index 0000000..34891e8 --- /dev/null +++ b/.mokogitea/workflows/custom/workflow-sync-trigger.yml @@ -0,0 +1,81 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: mokocli.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml +# VERSION: 01.01.00 +# BRIEF: Trigger workflow sync to live repos when a PR is merged to main + +name: "Universal: Workflow Sync Trigger" + +on: + workflow_dispatch: + pull_request: + types: [closed] + branches: + - main + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + sync: + name: Sync workflows to live repos + runs-on: ubuntu-latest + if: >- + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.merged == true && + !contains(github.event.pull_request.title, '[skip sync]')) + + steps: + - name: Determine platform from repo name + id: platform + run: | + REPO="${{ github.event.repository.name }}" + case "$REPO" in + Template-Joomla) PLATFORM="joomla" ;; + Template-Dolibarr) PLATFORM="dolibarr" ;; + Template-Go) PLATFORM="go" ;; + Template-MCP) PLATFORM="mcp" ;; + Template-Generic) PLATFORM="" ;; + *) PLATFORM="" ;; + esac + echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" + echo "Platform: ${PLATFORM:-all}" + + - name: Clone mokocli + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + run: | + MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" + git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli + + - name: Install PHP + run: | + if ! command -v php &> /dev/null; then + apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1 + fi + + - name: Install dependencies + run: | + cd /tmp/mokocli + composer install --no-dev --no-interaction --quiet 2>/dev/null || true + + - name: Run workflow sync + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + run: | + ARGS="--token ${MOKOGITEA_TOKEN}" + ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}" + ARGS="${ARGS} --phase repos" + + PLATFORM="${{ steps.platform.outputs.platform }}" + if [ -n "$PLATFORM" ]; then + ARGS="${ARGS} --platform-filter ${PLATFORM}" + fi + + php /tmp/mokocli/cli/workflow_sync.php ${ARGS} diff --git a/.mokogitea/workflows/pr-metadata-check.yml b/.mokogitea/workflows/pr-metadata-check.yml new file mode 100644 index 0000000..b4c9cbd --- /dev/null +++ b/.mokogitea/workflows/pr-metadata-check.yml @@ -0,0 +1,71 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: mokocli.Validation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template +# VERSION: 01.00.00 +# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs + +name: "Joomla: Metadata Validation" + +on: + pull_request: + types: [opened, synchronize, reopened, converted_to_draft, ready_for_review] + +permissions: + contents: read + +env: + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} + GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} + +jobs: + validate-metadata: + name: "Validate Joomla Metadata" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup mokocli tools + env: + MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting + run: | + if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then + echo Using pre-installed /opt/mokocli + echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV + else + echo Falling back to fresh clone + if ! command -v composer > /dev/null 2>&1; then + sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 + fi + rm -rf /tmp/mokocli + CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git + git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli + cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet + echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV + fi + + - name: Validate metadata against Joomla manifest + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + run: | + php ${MOKO_CLI}/joomla_metadata_validate.php \ + --path . \ + --token "${MOKOGITEA_TOKEN}" \ + --org "${GITEA_ORG}" \ + --repo "${GITEA_REPO}" \ + --api-base "${MOKOGITEA_URL}/api/v1" \ + --ci + + if [ $? -ne 0 ]; then + echo "::error::Joomla metadata mismatch — update delivery will fail. Run 'php cli/joomla_metadata_validate.php' locally to see details." + exit 1 + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index faf4dde..daf4293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ -## [Unreleased] +## [01.00.00] --- 2026-06-03 ### Changed - Migrated all workflow and template paths from `.github/` to `.mokogitea/` diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 1f97385..809e983 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -11,10 +11,10 @@ You should have received a copy of the GNU General Public License (./LICENSE). # FILE INFORMATION - DEFGROUP: MokoStandards-Template-Joomla-Plugin - INGROUP: MokoStandards-Template-Joomla-Plugin.Documentation - REPO: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin/ - VERSION: 01.00.00 + DEFGROUP: Template-Joomla + INGROUP: Template-Joomla.Documentation + REPO: https://github.com/mokoconsulting-tech/Template-Joomla/ + VERSION: 01.01.00 PATH: ./CODE_OF_CONDUCT.md BRIEF: Community expectations and enforcement guidelines NOTE: Adapted with attribution from the Contributor Covenant v2.1 diff --git a/GOVERNANCE.md b/GOVERNANCE.md index e7b8a57..47fa254 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -16,12 +16,12 @@ You should have received a copy of the GNU General Public License (./LICENSE). FILE INFORMATION - DEFGROUP: mokoconsulting-tech.MokoStandards-Template-Joomla-Plugin + DEFGROUP: mokoconsulting-tech.Template-Joomla INGROUP: MokoStandards.Governance - REPO: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin - VERSION: 04.00.04 + REPO: https://github.com/mokoconsulting-tech/Template-Joomla + VERSION: 01.01.00 PATH: /GOVERNANCE.md - BRIEF: Project governance rules, roles, and decision process for MokoStandards-Template-Joomla-Plugin + BRIEF: Project governance rules, roles, and decision process for Template-Joomla --> [![MokoStandards](https://img.shields.io/badge/MokoStandards-04.00.04-blue)](https://github.com/mokoconsulting-tech/MokoStandards) @@ -30,7 +30,7 @@ ## Overview -This document defines the governance model for the `MokoStandards-Template-Joomla-Plugin` repository within the +This document defines the governance model for the `Template-Joomla` repository within the `mokoconsulting-tech` organization. It is automatically maintained by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) v04.00.04. @@ -97,7 +97,7 @@ See the full policy: ## Reporting Issues -- **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin/issues) +- **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/Template-Joomla/issues) - **Security vulnerabilities**: See [SECURITY.md](./SECURITY.md) - **Code of Conduct**: See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) - **Contact**: dev@mokoconsulting.tech @@ -110,10 +110,10 @@ See the full policy: | ------------- | ----------------------------------------------- | | Document Type | Policy | | Domain | Governance | -| Applies To | mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin | +| Applies To | mokoconsulting-tech/Template-Joomla | | Jurisdiction | Tennessee, USA | | Maintainer | @mokoconsulting-tech | | Standards | MokoStandards v04.00.04 | -| Repo | https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin | +| Repo | https://github.com/mokoconsulting-tech/Template-Joomla | | Path | /GOVERNANCE.md | | Status | Active — auto-maintained by MokoStandards | diff --git a/Makefile b/Makefile deleted file mode 100644 index 3ebe6a2..0000000 --- a/Makefile +++ /dev/null @@ -1,172 +0,0 @@ -# Makefile for Joomla Extensions -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# MokoJoomGallery — Photo gallery management for Joomla - -# ============================================================================== -# CONFIGURATION - Customize these for your extension -# ============================================================================== - -# Extension Configuration -EXTENSION_NAME := mokojoomgallery -EXTENSION_TYPE := package -# Options: module, plugin, component, package, template -EXTENSION_VERSION := 1.0.0 - -# Module Configuration (for modules only) -MODULE_TYPE := site -# Options: site, admin - -# Plugin Configuration (for plugins only) -PLUGIN_GROUP := system -# Options: system, content, user, authentication, etc. - -# Directories -SRC_DIR := src -BUILD_DIR := build -DIST_DIR := dist -DOCS_DIR := docs - -# Joomla Installation (for local testing - customize paths) -JOOMLA_ROOT := /var/www/html/joomla -JOOMLA_VERSION := 4 - -# Tools -PHP := php -COMPOSER := composer -NPM := npm -PHPCS := vendor/bin/phpcs -PHPCBF := vendor/bin/phpcbf -PHPUNIT := vendor/bin/phpunit -ZIP := zip - -# Coding Standards -PHPCS_STANDARD := Joomla - -# Colors for output -COLOR_RESET := \033[0m -COLOR_GREEN := \033[32m -COLOR_YELLOW := \033[33m -COLOR_BLUE := \033[34m -COLOR_RED := \033[31m - -# ============================================================================== -# TARGETS -# ============================================================================== - -.PHONY: help -help: ## Show this help message - @echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)" - @echo "$(COLOR_BLUE)║ Joomla Extension Makefile ║$(COLOR_RESET)" - @echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)" - @echo "" - @echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)" - @echo "" - @echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}' - @echo "" - -.PHONY: install-deps -install-deps: ## Install all dependencies (Composer + npm) - @echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) install; \ - echo "$(COLOR_GREEN)✓ Composer dependencies installed$(COLOR_RESET)"; \ - fi - -.PHONY: lint -lint: ## Run PHP linter (syntax check) - @echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)" - @find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" \ - -exec $(PHP) -l {} \; | grep -v "No syntax errors" || true - @echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)" - -.PHONY: phpcs -phpcs: ## Run PHP CodeSniffer (Joomla standards) - @echo "$(COLOR_BLUE)Running PHP CodeSniffer...$(COLOR_RESET)" - @if [ -f "$(PHPCS)" ]; then \ - $(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHP CodeSniffer not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: validate -validate: lint phpcs ## Run all validation checks - @echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)" - -.PHONY: clean -clean: ## Clean build artifacts - @echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)" - @rm -rf $(BUILD_DIR) $(DIST_DIR) - @echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)" - -MOKO_PLATFORM ?= $(or $(wildcard ../moko-platform),$(wildcard $(HOME)/moko-platform),$(wildcard /opt/moko-platform)) -MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js - -.PHONY: minify -minify: ## Minify CSS/JS assets - @echo "Minifying assets..." - @if [ -f "$(MINIFY_SCRIPT)" ]; then \ - node "$(MINIFY_SCRIPT)" $(SRC_DIR); \ - elif [ -f "scripts/minify.js" ]; then \ - node scripts/minify.js; \ - else \ - echo "No minify script found"; \ - fi - -.PHONY: build -build: clean minify ## Build extension package - @echo "$(COLOR_BLUE)Building Joomla package extension...$(COLOR_RESET)" - @mkdir -p $(DIST_DIR) $(BUILD_DIR)/packages - - @# --- Build each sub-extension as a separate ZIP --- - @for EXT_DIR in $(SRC_DIR)/packages/*/; do \ - EXT_NAME=$$(basename "$$EXT_DIR"); \ - [ "$$EXT_NAME" = "index.html" ] && continue; \ - echo " Packaging $$EXT_NAME..."; \ - cd "$$EXT_DIR" && $(ZIP) -r "$(CURDIR)/$(BUILD_DIR)/packages/$${EXT_NAME}.zip" . \ - -x "*.git*" -x "*/index.html" 2>/dev/null; \ - cd "$(CURDIR)"; \ - done - - @# --- Build the outer package ZIP --- - @echo " Assembling pkg_$(EXTENSION_NAME)..." - @cp $(SRC_DIR)/pkg_mokojoomgallery.xml $(BUILD_DIR)/pkg_mokojoomgallery.xml - @cp $(SRC_DIR)/script.php $(BUILD_DIR)/script.php - @[ -d "$(SRC_DIR)/language" ] && cp -r $(SRC_DIR)/language $(BUILD_DIR)/language || true - @cd $(BUILD_DIR) && $(ZIP) -r "$(CURDIR)/$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" \ - pkg_mokojoomgallery.xml script.php language/ packages/ - - @echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)" - @echo " Contents:" - @unzip -l "$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" | tail -n +4 | head -20 - -.PHONY: package -package: build ## Alias for build - @echo "$(COLOR_GREEN)✓ Package ready for distribution$(COLOR_RESET)" - -.PHONY: release -release: validate build ## Create a release (validate + build) - @echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)" - -.PHONY: version -version: ## Display version information - @echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)" - @echo " Name: $(EXTENSION_NAME)" - @echo " Type: $(EXTENSION_TYPE)" - @echo " Version: $(EXTENSION_VERSION)" - -.PHONY: security-check -security-check: ## Run security checks on dependencies - @echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \ - fi - -.PHONY: all -all: install-deps validate build ## Run complete build pipeline - @echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)" - -# Default target -.DEFAULT_GOAL := help diff --git a/README.md b/README.md index cbff3b5..9a425d0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MokoStandards-Template-Joomla +# Template-Joomla Unified scaffolding templates for all Joomla extension types. diff --git a/SECURITY.md b/SECURITY.md index 11bc0b8..81c22ff 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,11 +19,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . # FILE INFORMATION -DEFGROUP: MokoStandards-Template-Joomla-Plugin -INGROUP: MokoStandards-Template-Joomla-Plugin.Documentation -REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-Template-Joomla-Plugin +DEFGROUP: Template-Joomla +INGROUP: Template-Joomla.Documentation +REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 01.00.00 +VERSION: 01.01.00 BRIEF: Security vulnerability reporting and handling policy --> @@ -54,7 +54,7 @@ Report security vulnerabilities privately to: **Email**: `security@mokoconsulting.tech` -**Subject Line**: `[SECURITY] MokoStandards-Template-Joomla-Plugin - Brief Description` +**Subject Line**: `[SECURITY] Template-Joomla - Brief Description` ### What to Include @@ -228,7 +228,7 @@ The following are explicitly out of scope: | ------------ | ------------------------------------------------------------------------------------------------------------ | | Document | Security Policy | | Path | /SECURITY.md | -| Repository | [https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin](https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin) | +| Repository | [https://github.com/mokoconsulting-tech/Template-Joomla](https://github.com/mokoconsulting-tech/Template-Joomla) | | Owner | Moko Consulting | | Scope | Security vulnerability handling | | Status | Active | diff --git a/automation/ci-issue-reporter.sh b/automation/ci-issue-reporter.sh deleted file mode 100644 index 65c47ba..0000000 --- a/automation/ci-issue-reporter.sh +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Automation.CI -# INGROUP: moko-platform.Automation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform -# PATH: /automation/ci-issue-reporter.sh -# VERSION: 09.23.00 -# BRIEF: Creates or updates a Gitea issue when a CI gate fails. -# Deduplicates by searching open issues with the "ci-auto" label -# whose title matches the gate. If a matching issue exists, a comment -# is appended instead of opening a duplicate. -# ============================================================================ - -set -euo pipefail - -# ── Defaults ──────────────────────────────────────────────────────────────── -GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}" -GITEA_TOKEN="${GITEA_TOKEN:-}" -REPO="${GITHUB_REPOSITORY:-}" -RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}" -LABEL_NAME="ci-auto" -LABEL_COLOR="#e11d48" - -GATE="" -DETAILS="" -SEVERITY="error" -WORKFLOW="" - -# ── Parse arguments ───────────────────────────────────────────────────────── -usage() { - cat </dev/null || echo "000") - - if [[ "$exists" == "200" ]]; then - # Check if label already exists - local found - found=$(curl -sf \ - -H "Authorization: token ${GITEA_TOKEN}" \ - "${API}/labels" 2>/dev/null \ - | grep -o "\"name\":\"${LABEL_NAME}\"" || true) - - if [[ -z "$found" ]]; then - curl -sf -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/labels" \ - -d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \ - > /dev/null 2>&1 || true - fi - fi -} - -# ── Search for existing open issue ────────────────────────────────────────── -find_existing_issue() { - # URL-encode the gate name for the query - local query - query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g') - - local response - response=$(curl -sf \ - -H "Authorization: token ${GITEA_TOKEN}" \ - "${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \ - 2>/dev/null || echo "[]") - - # Extract the first matching issue number - echo "$response" \ - | grep -oP '"number":\s*\K[0-9]+' \ - | head -1 -} - -# ── Build issue body ──────────────────────────────────────────────────────── -build_body() { - local severity_badge - if [[ "$SEVERITY" == "error" ]]; then - severity_badge="**Severity:** Error" - else - severity_badge="**Severity:** Warning" - fi - - cat </dev/null) - - HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/issues/${EXISTING}/comments" \ - -d "${COMMENT_JSON}" 2>/dev/null || echo "000") - - if [[ "$HTTP" == "201" ]]; then - echo "Commented on existing issue #${EXISTING}" - else - echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})" - fi -else - # Create new issue - ISSUE_BODY=$(build_body) - ISSUE_JSON=$(python3 -c " -import sys, json -body = sys.stdin.read() -print(json.dumps({ - 'title': sys.argv[1], - 'body': body, - 'labels': [] -}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null) - - # Create the issue - RESPONSE=$(curl -sf -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/issues" \ - -d "${ISSUE_JSON}" 2>/dev/null || echo "{}") - - ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1) - - if [[ -n "$ISSUE_NUM" ]]; then - # Apply label (separate call — more reliable across Gitea versions) - LABEL_ID=$(curl -sf \ - -H "Authorization: token ${GITEA_TOKEN}" \ - "${API}/labels" 2>/dev/null \ - | grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \ - | head -1 || true) - - if [[ -n "$LABEL_ID" ]]; then - curl -sf -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/issues/${ISSUE_NUM}/labels" \ - -d "{\"labels\":[${LABEL_ID}]}" \ - > /dev/null 2>&1 || true - fi - - echo "Created issue #${ISSUE_NUM}: ${TITLE}" - else - echo "WARNING: Failed to create issue" - echo "Response: ${RESPONSE}" - fi -fi