Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d3de97843 | |||
| 6333189313 | |||
| 49a00185c9 | |||
| e5e32c4cb7 | |||
| b0f29cefa3 | |||
| f2be11a5c3 | |||
| 048d1f4914 | |||
| 1cbdaa0c37 | |||
| 32e34fc936 | |||
| d7e679b7b2 | |||
| 23e777d50d | |||
| 14e177f862 | |||
| a8a9e1d31f | |||
| 83bb3fd084 | |||
| 6703935d80 | |||
| 28a9e05cb7 | |||
| 20293f5f5c | |||
| ec07a75097 | |||
| 6c4db0c64c | |||
| 327d4b4e85 | |||
| d886a83c9f | |||
| 508d33df63 | |||
| a625806870 | |||
| 391ce25256 | |||
| 170deab90f | |||
| bd13cb9a5e | |||
| 5b16c394dd | |||
| d8b76be9aa | |||
| e5d6936a94 | |||
| 2c79f527c4 | |||
| a4009aff2f | |||
| cf15759c3a | |||
| a9181197d1 | |||
| 7bd5b4cd87 | |||
| d33b6e37b2 | |||
| b56768c9e6 | |||
| 475d2e33ef | |||
| eaa97e6406 | |||
| beed1e628a | |||
| d11ca3d85b | |||
| d7fdd99f68 | |||
| 405261a123 | |||
| a93794f1ba | |||
| 0a095f9a6b | |||
| fe3644204a | |||
| 5a8bb1bd6e | |||
| 34ef05bd6e | |||
| c893f5ac33 | |||
| e177971462 | |||
| a28236d120 | |||
| 51e599acef | |||
| c73675234b | |||
| 16ff7e611e | |||
| 030f057ab4 | |||
| fec5464c17 | |||
| 2723fbf0e7 | |||
| e9dcd48e44 | |||
| 1bd170c77f | |||
| 1e18d6bcb8 | |||
| 35befccf06 | |||
| d012bb900b | |||
| 2ab332161d | |||
| a9acb2d27c | |||
| e609a4e205 | |||
| dbf4cdef9a | |||
| 24d5238b64 | |||
| 4ccefec2dc | |||
| 5a8b18ea8d | |||
| 9dbf790e1a | |||
| a07d93b6fc | |||
| 415e58d06c | |||
| d8ec7b5ba0 | |||
| e882425f04 | |||
| 3171fb3ef0 | |||
| 6cd46f0b7f | |||
| 48ae7c1e88 | |||
| 63a2640254 | |||
| 6ec2202c6e | |||
| 8f7cce051b | |||
| f426f21f2e | |||
| 2ee5a55ec5 | |||
| a04040533c | |||
| d5541abf22 | |||
| 8ae829ad89 | |||
| 5815ad040f | |||
| 96b6db73a9 | |||
| 8eb3e310cf | |||
| eca475c6e3 | |||
| 92822303ef | |||
| 9649fb55cf | |||
| 8f39017b59 | |||
| bd18642045 | |||
| 820e968e1a | |||
| a5cd566dea | |||
| b5599579a7 | |||
| 61a232dfc6 | |||
| a45bf42335 | |||
| 77a1ae3977 | |||
| fb5461b661 | |||
| e15421699e | |||
| 48d574e225 | |||
| 1dba0c37b9 | |||
| 07ea171af9 | |||
| 420b4f5f3c | |||
| f8c28f055b | |||
| a7df4d49b9 | |||
| 320b2c57be | |||
| d323ca52af | |||
| c5e4b41100 | |||
| 335fcd0382 | |||
| c1c820bb5c | |||
| f441a8a51f | |||
| 005eb5cf39 | |||
| 21acb19fed | |||
| 1fe4f83e73 | |||
| 7e5c322792 | |||
| b010677d75 | |||
| 9275e581c2 | |||
| 3f3b1f79a0 | |||
| 83842c50ad | |||
| fbedd5966c | |||
| eca2c13018 | |||
| 48d000107d | |||
| 7ceb9528cc | |||
| 5fabaec477 | |||
| e40b799101 | |||
| 7e9784e723 | |||
| 209dee14fd | |||
| 81351f45fd | |||
| fd451b4b73 | |||
| d0dbd1dceb | |||
| 3e2e291819 | |||
| 5975ea38d8 | |||
| 8ad548f4a3 | |||
| cbb4d73df5 | |||
| 47cb47ebdb | |||
| 22b0f8af7e | |||
| 08ca1429ae | |||
| e8da1a30ff | |||
| fb754b1a07 | |||
| 9a2c164207 | |||
| 78c1329a83 | |||
| 05f43ed88f | |||
| 05e4f39e7d | |||
| 3dcb3b6d3a | |||
| db4e6f5c6b | |||
| aa7fc45a67 | |||
| 03fe66238f | |||
| a5ae616a94 | |||
| ff7924de7d |
@@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
moko-platform Repository Manifest
|
|
||||||
Auto-generated by cleanup script.
|
|
||||||
See: https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home
|
|
||||||
-->
|
|
||||||
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
|
|
||||||
<identity>
|
|
||||||
<name>moko-platform</name>
|
|
||||||
<org>MokoConsulting</org>
|
|
||||||
<description>Enterprise automation, validation, sync, and governance engine for all Moko Consulting repositories</description>
|
|
||||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
|
||||||
</identity>
|
|
||||||
<governance>
|
|
||||||
<platform>generic</platform>
|
|
||||||
<standards-version>05.00.00</standards-version>
|
|
||||||
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/moko-platform</standards-source>
|
|
||||||
<last-synced>2026-05-10T19:51:08+00:00</last-synced>
|
|
||||||
</governance>
|
|
||||||
<build>
|
|
||||||
<language>HCL</language>
|
|
||||||
<package-type>generic</package-type>
|
|
||||||
<entry-point>src/</entry-point>
|
|
||||||
</build>
|
|
||||||
</moko-platform>
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Release
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||||
|
# VERSION: 09.02.00
|
||||||
|
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||||
|
|
||||||
|
name: "Universal: Auto Version Bump"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump:
|
||||||
|
name: Version Bump
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||||
|
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||||
|
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; 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
|
||||||
|
if [ -d "/opt/moko-platform/cli" ]; then
|
||||||
|
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||||
|
/tmp/moko-platform-api
|
||||||
|
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||||
|
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Bump version
|
||||||
|
run: |
|
||||||
|
BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true
|
||||||
|
echo "$BUMP"
|
||||||
|
|
||||||
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true
|
||||||
|
[ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; }
|
||||||
|
|
||||||
|
# Propagate to platform manifests with -dev suffix
|
||||||
|
php ${MOKO_CLI}/version_set_platform.php \
|
||||||
|
--path . --version "$VERSION" --branch dev --stability dev 2>/dev/null || true
|
||||||
|
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
VERSION="${VERSION}-dev"
|
||||||
|
|
||||||
|
# Commit if anything changed
|
||||||
|
if git diff --quiet && git diff --cached --quiet; then
|
||||||
|
echo "No version changes to commit"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push origin dev
|
||||||
|
echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -26,13 +26,20 @@
|
|||||||
name: "Universal: Build & Release"
|
name: "Universal: Build & Release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
|
types: [opened, closed]
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
|
||||||
- 'src/**'
|
|
||||||
- 'htdocs/**'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
action:
|
||||||
|
description: 'Action to perform'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: release
|
||||||
|
options:
|
||||||
|
- release
|
||||||
|
- promote-rc
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
@@ -44,28 +51,94 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
# ── Draft PR → Promote highest pre-release to RC ─────────────────────────────
|
||||||
name: Build & Release Pipeline
|
promote-rc:
|
||||||
|
name: Promote Pre-Release to RC
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
if: >-
|
||||||
|
(github.event.action == 'opened' && github.event.pull_request.draft == true) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup moko-platform tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; 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
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
|
/tmp/moko-platform-api
|
||||||
|
cd /tmp/moko-platform-api
|
||||||
|
composer install --no-dev --no-interaction --quiet
|
||||||
|
|
||||||
|
- name: Promote to release-candidate
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/release_promote.php \
|
||||||
|
--from auto --to release-candidate \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
--api-base "${API_BASE}" \
|
||||||
|
--branch "${{ github.event.pull_request.head.ref || 'dev' }}"
|
||||||
|
|
||||||
|
- name: Cascade lesser channels
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/release_cascade.php \
|
||||||
|
--stability release-candidate \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
--api-base "${API_BASE}"
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Draft PR opened — promoted highest pre-release to RC" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
||||||
|
release:
|
||||||
|
name: Build & Release Pipeline
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure git for bot pushes
|
||||||
|
run: |
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||||
run: |
|
run: |
|
||||||
# Ensure PHP + Composer are available
|
# Ensure PHP + Composer are available
|
||||||
if ! command -v composer &> /dev/null; then
|
if ! command -v composer &> /dev/null; 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
|
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
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
/tmp/moko-platform-api
|
/tmp/moko-platform-api
|
||||||
@@ -92,20 +165,44 @@ jobs:
|
|||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
# Strip any pre-release suffix merged from dev (e.g. 01.02.20-dev → 01.02.20)
|
||||||
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
echo "branch=main" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: "Step 1b: Bump version"
|
# -- CHECK FOR RC PROMOTION ------------------------------------------------
|
||||||
id: bump
|
- name: "Check for RC release"
|
||||||
|
id: rc
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
RC_JSON=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
"${API_BASE}/releases/tags/release-candidate" 2>/dev/null || echo "{}")
|
||||||
|
RC_ID=$(echo "$RC_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$RC_ID" ] && [ "$RC_ID" != "None" ] && [ "$RC_ID" != "" ]; then
|
||||||
|
echo "promote=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "release_id=${RC_ID}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "::notice::RC release found (id: ${RC_ID}) — will promote to stable"
|
||||||
|
else
|
||||||
|
echo "promote=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "::notice::No RC release — full build pipeline"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: "Step 1b: Minor bump version"
|
||||||
|
id: bump
|
||||||
|
if: >-
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
steps.rc.outputs.promote != 'true'
|
||||||
run: |
|
run: |
|
||||||
MOKO_API="/tmp/moko-platform-api/cli"
|
MOKO_API="/tmp/moko-platform-api/cli"
|
||||||
BUMP=$(php ${MOKO_API}/version_bump.php --path . --minor)
|
php ${MOKO_API}/version_bump.php --path . --minor 2>&1 || true
|
||||||
VERSION=$(echo "$BUMP" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
|
VERSION=$(php ${MOKO_API}/version_read.php --path .)
|
||||||
[ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
|
# Strip any pre-release suffix — stable releases have no suffix
|
||||||
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "Bumped to: ${VERSION}"
|
echo "Bumped to: ${VERSION}"
|
||||||
|
|
||||||
@@ -135,95 +232,8 @@ jobs:
|
|||||||
steps.check.outputs.already_released != 'true'
|
steps.check.outputs.already_released != 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
ERRORS=0
|
php /tmp/moko-platform-api/cli/release_validate.php \
|
||||||
|
--path . --version "$VERSION" --output-summary --github-output || true
|
||||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
||||||
MANIFEST="${{ steps.platform.outputs.manifest }}"
|
|
||||||
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
|
|
||||||
echo "## Pre-Release Sanity Checks (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# -- Version drift check (must pass before release) --------
|
|
||||||
README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
|
|
||||||
if [ "$README_VER" != "$VERSION" ]; then
|
|
||||||
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
else
|
|
||||||
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check CHANGELOG version matches
|
|
||||||
CL_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' CHANGELOG.md 2>/dev/null | head -1)
|
|
||||||
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
|
|
||||||
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check composer.json version if present
|
|
||||||
if [ -f "composer.json" ]; then
|
|
||||||
COMP_VER=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' composer.json 2>/dev/null | head -1)
|
|
||||||
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
|
|
||||||
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Common checks
|
|
||||||
if [ ! -f "LICENSE" ]; then
|
|
||||||
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
else
|
|
||||||
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
|
|
||||||
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -- Platform-specific checks --------
|
|
||||||
case "$PLATFORM" in
|
|
||||||
joomla)
|
|
||||||
if [ -n "$MANIFEST" ]; then
|
|
||||||
XML_VER=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
|
||||||
if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
|
|
||||||
echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
else
|
|
||||||
echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null)
|
|
||||||
echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "- No Joomla XML manifest (WaaS site)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi ;;
|
|
||||||
dolibarr)
|
|
||||||
if [ -n "$MOD_FILE" ]; then
|
|
||||||
MOD_VER=$(sed -n "s/.*\\\$this->version = '\([^']*\)'.*/\1/p" "$MOD_FILE" 2>/dev/null | head -1)
|
|
||||||
if [ -n "$MOD_VER" ] && [ "$MOD_VER" != "$VERSION" ]; then
|
|
||||||
echo "- Module drift: \`${MOD_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
else
|
|
||||||
echo "- Module version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "- No mod*.class.php found" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
fi
|
|
||||||
if [ ! -f "update.txt" ]; then
|
|
||||||
echo "- Missing update.txt" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
fi ;;
|
|
||||||
*) echo "- Generic platform � no manifest checks" >> $GITHUB_STEP_SUMMARY ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
if [ "$ERRORS" -gt 0 ]; then
|
|
||||||
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
|
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
|
||||||
# Always runs — every version change on main archives to version/XX.YY
|
# Always runs — every version change on main archives to version/XX.YY
|
||||||
@@ -261,17 +271,21 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
php /tmp/moko-platform-api/cli/badge_update.php --path . --version "${VERSION}" 2>/dev/null || true
|
php /tmp/moko-platform-api/cli/badge_update.php --path . --version "${VERSION}" 2>/dev/null || true
|
||||||
|
php /tmp/moko-platform-api/cli/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
- name: "Step 5: Write update stream"
|
# Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum
|
||||||
|
|
||||||
|
- name: "Step 4b: Promote and prune CHANGELOG"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
steps.platform.outputs.platform == 'joomla'
|
steps.check.outputs.already_released != 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
php /tmp/moko-platform-api/cli/updates_xml_build.php \
|
MOKO_API="/tmp/moko-platform-api/cli"
|
||||||
--path . --version "${VERSION}" --stability stable \
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
php ${MOKO_API}/changelog_promote.php --path . --version "$VERSION" 2>&1 || true
|
||||||
--github-output
|
php ${MOKO_API}/changelog_prune.php --path . --keep 5 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Commit release changes
|
- name: Commit release changes
|
||||||
if: >-
|
if: >-
|
||||||
@@ -283,21 +297,16 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
|
||||||
git config --local user.name "gitea-actions[bot]"
|
|
||||||
# Set push URL with token for branch-protected repos
|
|
||||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
git push -u origin HEAD
|
# Detached HEAD on PR merge — push explicitly to main
|
||||||
|
git push origin HEAD:refs/heads/main
|
||||||
|
|
||||||
# -- STEP 6: Create tag ---------------------------------------------------
|
# -- STEP 6: Create tag ---------------------------------------------------
|
||||||
- name: "Step 6: Create git tag"
|
- name: "Step 6: Create git tag"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true'
|
||||||
steps.check.outputs.tag_exists != 'true' &&
|
|
||||||
steps.version.outputs.is_minor == 'true'
|
|
||||||
run: |
|
run: |
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
# Only create the major release tag if it doesn't exist yet
|
# Only create the major release tag if it doesn't exist yet
|
||||||
@@ -310,374 +319,126 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- STEP 7: Create or update Gitea Release --------------------------------
|
# -- STEP 7a: Promote RC to stable (skip build) ----------------------------
|
||||||
- name: "Step 7: Gitea Release"
|
- name: "Step 7a: Promote RC to stable"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true'
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
steps.rc.outputs.promote == 'true'
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/release_promote.php \
|
||||||
|
--from release-candidate --to stable \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
--api-base "${API_BASE}" \
|
||||||
|
--path . --branch main
|
||||||
|
echo "Promoted RC → stable (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# -- STEP 7b: Create or update Gitea Release (full build path) -------------
|
||||||
|
- name: "Step 7b: Gitea Release"
|
||||||
|
if: >-
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
steps.rc.outputs.promote != 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
|
||||||
MAJOR="${{ steps.version.outputs.major }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/release_create.php \
|
||||||
|
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --branch main
|
||||||
|
echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Reuse metadata from Step 5 (single source of truth)
|
# -- STEP 8: Build packages and upload to release ----------------------------
|
||||||
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
|
- name: "Step 8: Build package and upload"
|
||||||
EXT_NAME="${{ steps.updates.outputs.ext_name }}"
|
id: package
|
||||||
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
|
|
||||||
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
|
|
||||||
|
|
||||||
# Fallbacks if Step 5 was skipped
|
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
|
||||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
||||||
fi
|
|
||||||
[ -z "$EXT_NAME" ] && EXT_NAME="${GITEA_REPO}"
|
|
||||||
|
|
||||||
NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
||||||
|
|
||||||
# Build release name: "Pretty Name VERSION (type_element-VERSION)"
|
|
||||||
TYPE_PREFIX=""
|
|
||||||
case "${EXT_TYPE}" in
|
|
||||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
|
||||||
module) TYPE_PREFIX="mod_" ;;
|
|
||||||
component) TYPE_PREFIX="com_" ;;
|
|
||||||
template) TYPE_PREFIX="tpl_" ;;
|
|
||||||
library) TYPE_PREFIX="lib_" ;;
|
|
||||||
package) TYPE_PREFIX="pkg_" ;;
|
|
||||||
esac
|
|
||||||
RELEASE_NAME="${EXT_NAME} ${VERSION} (${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION})"
|
|
||||||
|
|
||||||
# Delete existing release if present (overwrite, not append)
|
|
||||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
|
||||||
EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -n "$EXISTING_ID" ]; then
|
|
||||||
curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/${EXISTING_ID}" 2>/dev/null || true
|
|
||||||
curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/tags/${RELEASE_TAG}" 2>/dev/null || true
|
|
||||||
echo "Deleted previous stable release (id: ${EXISTING_ID})"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create fresh release
|
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/releases" \
|
|
||||||
-d "$(python3 -c "import json; print(json.dumps({
|
|
||||||
'tag_name': '${RELEASE_TAG}',
|
|
||||||
'name': '${RELEASE_NAME}',
|
|
||||||
'body': '''## ${VERSION} ($(date +%Y-%m-%d))\n${NOTES}''',
|
|
||||||
'target_commitish': '${BRANCH}'
|
|
||||||
}))")"
|
|
||||||
echo "Release created: ${RELEASE_NAME}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
|
||||||
- name: "Step 8: Build package and update checksum"
|
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true'
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
steps.rc.outputs.promote != 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
REPO="${{ github.repository }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/release_package.php \
|
||||||
|
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --output /tmp || true
|
||||||
|
|
||||||
# All ZIPs upload to the major release tag (vXX)
|
# -- STEP 5: Write update stream (after build so SHA-256 is available) -----
|
||||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
- name: "Step 5: Write update stream"
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
||||||
if [ -z "$RELEASE_ID" ]; then
|
|
||||||
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find extension element name from manifest
|
|
||||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
|
||||||
[ -z "$MANIFEST" ] && exit 0
|
|
||||||
|
|
||||||
# Reuse element from Step 5, with same fallback chain
|
|
||||||
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
|
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
|
||||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
|
||||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
|
||||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
|
||||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
||||||
fi
|
|
||||||
# ZIP name: type_folder_element-VERSION (e.g. plg_system_mokojgdpc-01.01.00.zip)
|
|
||||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
TYPE_PREFIX=""
|
|
||||||
case "${EXT_TYPE}" in
|
|
||||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
|
||||||
module) TYPE_PREFIX="mod_" ;;
|
|
||||||
component) TYPE_PREFIX="com_" ;;
|
|
||||||
template) TYPE_PREFIX="tpl_" ;;
|
|
||||||
library) TYPE_PREFIX="lib_" ;;
|
|
||||||
package) TYPE_PREFIX="pkg_" ;;
|
|
||||||
esac
|
|
||||||
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
|
|
||||||
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
|
|
||||||
|
|
||||||
# -- Build install packages from src/ ----------------------------
|
|
||||||
SOURCE_DIR="src"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/"; exit 0; }
|
|
||||||
|
|
||||||
# ZIP package (type-aware via moko-platform PHP API)
|
|
||||||
php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --output /tmp
|
|
||||||
# Match the expected ZIP_NAME for upload
|
|
||||||
BUILT_ZIP=$(ls /tmp/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip 2>/dev/null | head -1 || true)
|
|
||||||
if [ -n "$BUILT_ZIP" ] && [ "$BUILT_ZIP" != "/tmp/${ZIP_NAME}" ]; then
|
|
||||||
mv "$BUILT_ZIP" "/tmp/${ZIP_NAME}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# tar.gz package (flat source archive)
|
|
||||||
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" --exclude='.ftpignore' --exclude='sftp-config*' --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
|
|
||||||
|
|
||||||
ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown")
|
|
||||||
TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
# -- Calculate SHA-256 for both ----------------------------------
|
|
||||||
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
|
||||||
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
|
||||||
|
|
||||||
# -- Delete existing assets with same name before uploading ------
|
|
||||||
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
|
|
||||||
for ASSET_NAME in "$ZIP_NAME" "$TAR_NAME"; do
|
|
||||||
ASSET_ID=$(echo "$ASSETS" | python3 -c "
|
|
||||||
import sys,json
|
|
||||||
assets = json.load(sys.stdin)
|
|
||||||
for a in assets:
|
|
||||||
if a['name'] == '${ASSET_NAME}':
|
|
||||||
print(a['id']); break
|
|
||||||
" 2>/dev/null || true)
|
|
||||||
if [ -n "$ASSET_ID" ]; then
|
|
||||||
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# -- Upload both to release tag ----------------------------------
|
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
--data-binary @"/tmp/${ZIP_NAME}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" > /dev/null 2>&1 || true
|
|
||||||
|
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
--data-binary @"/tmp/${TAR_NAME}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
|
|
||||||
|
|
||||||
# -- Update updates.xml with both download formats ---------------
|
|
||||||
if [ -f "updates.xml" ]; then
|
|
||||||
ZIP_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
|
||||||
TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
|
||||||
|
|
||||||
# Use Python to update only the stable entry's downloads + sha256
|
|
||||||
export PY_ZIP_URL="$ZIP_URL" PY_TAR_URL="$TAR_URL" PY_SHA="$SHA256_ZIP"
|
|
||||||
python3 << 'PYEOF'
|
|
||||||
import re, os
|
|
||||||
|
|
||||||
with open("updates.xml") as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
zip_url = os.environ["PY_ZIP_URL"]
|
|
||||||
tar_url = os.environ["PY_TAR_URL"]
|
|
||||||
sha = os.environ["PY_SHA"]
|
|
||||||
|
|
||||||
# Find the stable update block and replace its downloads + sha256
|
|
||||||
def replace_stable(m):
|
|
||||||
block = m.group(0)
|
|
||||||
# Replace downloads block
|
|
||||||
new_downloads = (
|
|
||||||
" <downloads>\n"
|
|
||||||
f" <downloadurl type=\"full\" format=\"zip\">{zip_url}</downloadurl>\n"
|
|
||||||
" </downloads>"
|
|
||||||
)
|
|
||||||
block = re.sub(r' <downloads>.*?</downloads>', new_downloads, block, flags=re.DOTALL)
|
|
||||||
# Add or replace sha256
|
|
||||||
if '<sha256>' in block:
|
|
||||||
block = re.sub(r' <sha256>.*?</sha256>', f' <sha256>{sha}</sha256>', block)
|
|
||||||
else:
|
|
||||||
block = block.replace('</downloads>', f'</downloads>\n <sha256>{sha}</sha256>')
|
|
||||||
return block
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r' <update>.*?<tag>stable</tag>.*?</update>',
|
|
||||||
replace_stable,
|
|
||||||
content,
|
|
||||||
flags=re.DOTALL
|
|
||||||
)
|
|
||||||
|
|
||||||
with open("updates.xml", "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
|
||||||
git add updates.xml
|
|
||||||
git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \
|
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true
|
|
||||||
git push || true
|
|
||||||
|
|
||||||
# Sync updates.xml to main via direct API (always runs — may be on version/XX branch)
|
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
|
||||||
API="${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}"
|
|
||||||
|
|
||||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
|
||||||
|
|
||||||
if [ -n "$FILE_SHA" ]; then
|
|
||||||
CONTENT=$(base64 -w0 updates.xml)
|
|
||||||
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/contents/updates.xml" \
|
|
||||||
-d "$(jq -n \
|
|
||||||
--arg content "$CONTENT" \
|
|
||||||
--arg sha "$FILE_SHA" \
|
|
||||||
--arg msg "chore: sync updates.xml ${VERSION} [skip ci]" \
|
|
||||||
--arg branch "main" \
|
|
||||||
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
|
||||||
)" > /dev/null 2>&1 \
|
|
||||||
&& echo "updates.xml synced to main via API" \
|
|
||||||
|| echo "WARNING: failed to sync updates.xml to main"
|
|
||||||
else
|
|
||||||
echo "WARNING: could not get updates.xml SHA from main"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "### Packages" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Package | Size | SHA-256 |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|---------|------|---------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# -- STEP 8b: Update release description with changelog + SHA ----------------
|
|
||||||
- name: "Step 8b: Update release body with changelog and SHA"
|
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
|
|
||||||
|
# Fetch latest updates.xml from main so preserve logic has all channels
|
||||||
|
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/contents/updates.xml?ref=main" 2>/dev/null | \
|
||||||
|
python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
|
||||||
|
> updates.xml 2>/dev/null || true
|
||||||
|
|
||||||
|
SHA_FLAG=""
|
||||||
|
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||||
|
|
||||||
|
php /tmp/moko-platform-api/cli/updates_xml_build.php \
|
||||||
|
--path . --version "${VERSION}" --stability stable \
|
||||||
|
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||||
|
${SHA_FLAG} --github-output
|
||||||
|
|
||||||
|
# Commit updates.xml if changed
|
||||||
|
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||||
|
git add updates.xml
|
||||||
|
git commit -m "chore: update stable channel ${VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push origin HEAD:refs/heads/main 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -- STEP 8b: Update release description with changelog ----------------------
|
||||||
|
- name: "Step 8b: Update release body"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
php /tmp/moko-platform-api/cli/release_body_update.php \
|
||||||
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
|
--path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
|
||||||
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
|
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||||
|
2>&1 || true
|
||||||
# Build TYPE_PREFIX to match Step 8's ZIP naming
|
echo "Release body updated" >> $GITHUB_STEP_SUMMARY
|
||||||
TYPE_PREFIX=""
|
|
||||||
case "${EXT_TYPE}" in
|
|
||||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
|
||||||
module) TYPE_PREFIX="mod_" ;;
|
|
||||||
component) TYPE_PREFIX="com_" ;;
|
|
||||||
template) TYPE_PREFIX="tpl_" ;;
|
|
||||||
library) TYPE_PREFIX="lib_" ;;
|
|
||||||
package) TYPE_PREFIX="pkg_" ;;
|
|
||||||
esac
|
|
||||||
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
|
|
||||||
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
|
|
||||||
|
|
||||||
# Get SHA from the built files
|
|
||||||
SHA256_ZIP=""
|
|
||||||
[ -f "/tmp/${ZIP_NAME}" ] && SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
|
||||||
SHA256_TAR=""
|
|
||||||
[ -f "/tmp/${TAR_NAME}" ] && SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
|
||||||
|
|
||||||
# Extract latest changelog entry (strip the ## header to avoid duplicate)
|
|
||||||
CHANGELOG=""
|
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
|
||||||
CHANGELOG=$(sed -n "/^## \[*${VERSION}/,/^## \[*[0-9]/p" CHANGELOG.md | sed '$d' | sed '1d')
|
|
||||||
[ -z "$CHANGELOG" ] && CHANGELOG=$(sed -n '/^## /,/^## /p' CHANGELOG.md | sed '$d' | sed '1d' | head -30)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build release body (single header, no duplicate from changelog)
|
|
||||||
BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\n"
|
|
||||||
if [ -n "$CHANGELOG" ]; then
|
|
||||||
BODY="${BODY}${CHANGELOG}\n\n"
|
|
||||||
fi
|
|
||||||
BODY="${BODY}---\n\n### Checksums\n\n"
|
|
||||||
BODY="${BODY}| File | SHA-256 |\n|------|--------|\n"
|
|
||||||
[ -n "$SHA256_ZIP" ] && BODY="${BODY}| \`${ZIP_NAME}\` | \`${SHA256_ZIP}\` |\n"
|
|
||||||
[ -n "$SHA256_TAR" ] && BODY="${BODY}| \`${TAR_NAME}\` | \`${SHA256_TAR}\` |\n"
|
|
||||||
|
|
||||||
# Get release ID and update body
|
|
||||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null | \
|
|
||||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
|
|
||||||
python3 -c "
|
|
||||||
import json, urllib.request
|
|
||||||
body = '''$(printf '%b' "$BODY")'''
|
|
||||||
data = json.dumps({'body': body}).encode()
|
|
||||||
req = urllib.request.Request(
|
|
||||||
'${API_BASE}/releases/${RELEASE_ID}',
|
|
||||||
data=data,
|
|
||||||
headers={'Authorization': 'token ${{ secrets.GA_TOKEN }}', 'Content-Type': 'application/json'},
|
|
||||||
method='PATCH'
|
|
||||||
)
|
|
||||||
urllib.request.urlopen(req)
|
|
||||||
" 2>/dev/null && echo "Release body updated with changelog + SHA" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||||
- name: "Step 9: Mirror release to GitHub"
|
- name: "Step 9: Mirror release to GitHub"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
steps.version.outputs.stability == 'stable' &&
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
secrets.GH_TOKEN != ''
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
MAJOR="${{ steps.version.outputs.major }}"
|
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
|
php /tmp/moko-platform-api/cli/release_mirror.php \
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
echo "$NOTES" > /tmp/release_notes.md
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".tag_name // empty" || true)
|
--branch main 2>&1 || true
|
||||||
|
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||||
if [ -z "$EXISTING" ]; then
|
|
||||||
gh release create "$RELEASE_TAG" \
|
|
||||||
--repo "$GH_REPO" \
|
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
|
||||||
--notes-file /tmp/release_notes.md \
|
|
||||||
--target "$BRANCH" || true
|
|
||||||
else
|
|
||||||
gh release edit "$RELEASE_TAG" \
|
|
||||||
--repo "$GH_REPO" \
|
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload assets to GitHub mirror
|
|
||||||
for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do
|
|
||||||
if [ -f "$PKG" ]; then
|
|
||||||
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".id // empty")
|
|
||||||
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
||||||
- name: "Step 10: Push main to GitHub mirror"
|
- name: "Step 10: Push main to GitHub mirror"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
secrets.GH_TOKEN != ''
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||||
git remote add github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||||
git remote set-url github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||||
git fetch origin main --depth=1
|
git fetch origin main --depth=1
|
||||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||||
&& echo "main branch pushed to GitHub mirror" \
|
&& echo "main branch pushed to GitHub mirror" \
|
||||||
@@ -688,18 +449,20 @@ jobs:
|
|||||||
- name: "Delete lesser pre-release channels"
|
- name: "Delete lesser pre-release channels"
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/release_cascade.php \
|
php /tmp/moko-platform-api/cli/release_cascade.php \
|
||||||
--stability stable \
|
--stability stable \
|
||||||
--token "${{ secrets.GA_TOKEN }}" \
|
--version "${VERSION}" \
|
||||||
--org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--gitea-url "${GITEA_URL}" 2>/dev/null || true
|
--api-base "${API_BASE}" 2>/dev/null || true
|
||||||
|
|
||||||
- name: "Step 11: Delete and recreate dev branch from main"
|
- name: "Step 11: Delete and recreate dev branch from main"
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
# Delete dev branch
|
# Delete dev branch
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
@@ -713,28 +476,35 @@ jobs:
|
|||||||
|
|
||||||
echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
|
echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: "Step 12: Create version branch from main"
|
||||||
# -- Dolibarr post-release: Reset dev version -----------------------------
|
if: steps.version.outputs.skip != 'true'
|
||||||
- name: "Dolibarr: Reset dev version"
|
|
||||||
if: >-
|
|
||||||
steps.version.outputs.skip != 'true' &&
|
|
||||||
steps.platform.outputs.platform == 'dolibarr' &&
|
|
||||||
steps.platform.outputs.mod_file != ''
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
ENCODED_PATH=$(echo "$MOD_FILE" | sed 's|^\./||' | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))")
|
BRANCH_NAME="version/${VERSION}"
|
||||||
FILE_RESP=$(curl -sf -H "Authorization: token ${TOKEN}" "${API_BASE}/contents/${ENCODED_PATH}?ref=dev" 2>/dev/null || true)
|
MAIN_SHA=$(git rev-parse HEAD)
|
||||||
FILE_SHA=$(echo "$FILE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
|
||||||
FILE_CONTENT=$(echo "$FILE_RESP" | python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin).get('content','')).decode())" 2>/dev/null || true)
|
# Delete old version branch if it exists (same version re-release)
|
||||||
if [ -n "$FILE_SHA" ] && [ -n "$FILE_CONTENT" ]; then
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
||||||
UPDATED=$(echo "$FILE_CONTENT" | sed "s/\$this->version = '[^']*'/\$this->version = 'development'/")
|
|
||||||
ENCODED=$(echo "$UPDATED" | base64 -w0)
|
# Create version/XX.YY.ZZ from main
|
||||||
curl -sf -X PUT -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/contents/${ENCODED_PATH}" \
|
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
|
||||||
-d "$(jq -n --arg content \"$ENCODED\" --arg sha \"$FILE_SHA\" --arg msg \"chore(version): reset dev version [skip ci]\" --arg branch \"dev\" '{content:$content,sha:$sha,message:$msg,branch:$branch}')" > /dev/null 2>&1 || true
|
|
||||||
fi
|
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -- Dolibarr post-release: Reset dev version -----------------------------
|
||||||
|
- name: "Post-release: Reset dev version"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
php /tmp/moko-platform-api/cli/version_reset_dev.php \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||||
|
--branch dev --path . 2>&1 || true
|
||||||
|
|
||||||
# -- Summary --------------------------------------------------------------
|
# -- Summary --------------------------------------------------------------
|
||||||
- name: Pipeline Summary
|
- name: Pipeline Summary
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoStandards.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Delete feature branches after PR merge
|
||||||
|
|
||||||
|
name: "Branch Cleanup"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
name: Delete merged branch
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
github.event.pull_request.head.ref != 'dev' &&
|
||||||
|
github.event.pull_request.head.ref != 'main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Delete source branch
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||||
|
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||||
|
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BRANCH}', safe=''))")
|
||||||
|
|
||||||
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
|
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ "$STATUS" = "204" ]; then
|
||||||
|
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "$STATUS" = "404" ]; then
|
||||||
|
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
||||||
|
fi
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
- name: Discover target branches
|
- name: Discover target branches
|
||||||
id: branches
|
id: branches
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
ALL_BRANCHES=""
|
ALL_BRANCHES=""
|
||||||
while true; do
|
while true; do
|
||||||
BATCH=$(curl -sS \
|
BATCH=$(curl -sS \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/branches?page=${PAGE}&limit=50" \
|
"${API}/branches?page=${PAGE}&limit=50" \
|
||||||
| jq -r '.[].name // empty')
|
| jq -r '.[].name // empty')
|
||||||
[ -z "$BATCH" ] && break
|
[ -z "$BATCH" ] && break
|
||||||
@@ -93,7 +93,7 @@ jobs:
|
|||||||
- name: Cascade to all target branches
|
- name: Cascade to all target branches
|
||||||
if: steps.branches.outputs.targets != ''
|
if: steps.branches.outputs.targets != ''
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
SHORT_SHA="${GITHUB_SHA:0:7}"
|
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||||
@@ -111,7 +111,7 @@ jobs:
|
|||||||
# Check if branch is already up to date
|
# Check if branch is already up to date
|
||||||
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
|
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
|
||||||
RESPONSE=$(curl -sS \
|
RESPONSE=$(curl -sS \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/compare/${ENCODED_BRANCH}...main")
|
"${API}/compare/${ENCODED_BRANCH}...main")
|
||||||
|
|
||||||
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
|
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
|
|
||||||
# Check for existing cascade PR
|
# Check for existing cascade PR
|
||||||
EXISTING=$(curl -sS \
|
EXISTING=$(curl -sS \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
|
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
|
||||||
|
|
||||||
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
|
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
|
||||||
@@ -139,7 +139,7 @@ jobs:
|
|||||||
# Create cascade PR
|
# Create cascade PR
|
||||||
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||||
-X POST \
|
-X POST \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{
|
-d "{
|
||||||
\"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
|
\"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
|
||||||
@@ -165,7 +165,7 @@ jobs:
|
|||||||
|
|
||||||
# Try auto-merge
|
# Try auto-merge
|
||||||
PR_DATA=$(curl -sS \
|
PR_DATA=$(curl -sS \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/pulls/${PR_NUMBER}")
|
"${API}/pulls/${PR_NUMBER}")
|
||||||
|
|
||||||
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
|
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
|
||||||
@@ -178,7 +178,7 @@ jobs:
|
|||||||
|
|
||||||
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||||
-X POST \
|
-X POST \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{
|
-d "{
|
||||||
\"Do\": \"merge\",
|
\"Do\": \"merge\",
|
||||||
|
|||||||
@@ -124,16 +124,16 @@ jobs:
|
|||||||
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "PSR-12 compliance: passed" >> $GITHUB_STEP_SUMMARY
|
echo "PSR-12 compliance: passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
- name: "PHPStan (Level 2)"
|
- name: "PHPStan (Level 6)"
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
run: |
|
||||||
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --error-format=github 2>&1 || {
|
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --memory-limit=512M --error-format=github 2>&1 || {
|
||||||
echo "::warning::PHPStan found type errors (advisory)"
|
echo "::error::PHPStan found type errors"
|
||||||
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Static analysis errors detected. Run \`composer phpstan\` locally." >> $GITHUB_STEP_SUMMARY
|
echo "Static analysis errors detected. Run \`composer phpstan\` locally." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Static analysis: advisory (level 0)" >> $GITHUB_STEP_SUMMARY
|
echo "Static analysis (level 6): passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
- name: "Psalm"
|
- name: "Psalm"
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -177,11 +177,14 @@ jobs:
|
|||||||
|
|
||||||
- name: "PHPUnit (PHP ${{ matrix.php }})"
|
- name: "PHPUnit (PHP ${{ matrix.php }})"
|
||||||
run: |
|
run: |
|
||||||
vendor/bin/phpunit --testdox 2>&1
|
vendor/bin/phpunit --testdox 2>&1 || {
|
||||||
{
|
echo "::error::PHPUnit tests failed"
|
||||||
echo "### PHPUnit (PHP ${{ matrix.php }})"
|
echo "### PHPUnit (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "All tests passed."
|
echo "Tests failed. Run \`vendor/bin/phpunit --testdox\` locally." >> $GITHUB_STEP_SUMMARY
|
||||||
} >> $GITHUB_STEP_SUMMARY
|
exit 1
|
||||||
|
}
|
||||||
|
echo "### PHPUnit (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
# Gate 3 — Self-Health (Dogfood)
|
# Gate 3 — Self-Health (Dogfood)
|
||||||
|
|||||||
@@ -33,17 +33,17 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
|
||||||
- name: Delete merged branches
|
- name: Delete merged branches
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Merged Branch Cleanup ==="
|
echo "=== Merged Branch Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
# List branches via API
|
# List branches via API
|
||||||
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
# Check if branch is merged into main
|
# Check if branch is merged into main
|
||||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||||
echo " Deleting merged branch: ${BRANCH}"
|
echo " Deleting merged branch: ${BRANCH}"
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
fi
|
fi
|
||||||
@@ -66,20 +66,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Clean old workflow runs
|
- name: Clean old workflow runs
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Workflow Run Cleanup ==="
|
echo "=== Workflow Run Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
|
||||||
# Get old completed runs
|
# Get old completed runs
|
||||||
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/actions/runs?status=completed&limit=50" | \
|
"${API}/actions/runs?status=completed&limit=50" | \
|
||||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
for RUN_ID in $RUNS; do
|
for RUN_ID in $RUNS; do
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: moko-platform.Deploy
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
||||||
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
|
|
||||||
# VERSION: 04.07.00
|
|
||||||
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
|
|
||||||
|
|
||||||
name: "Universal: Deploy to Dev (Manual)"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
clear_remote:
|
|
||||||
description: 'Delete all remote files before uploading'
|
|
||||||
required: false
|
|
||||||
default: 'false'
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: SFTP Deploy to Dev
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
run: |
|
|
||||||
php -v && composer --version
|
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
|
||||||
env:
|
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
|
||||||
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
|
||||||
run: |
|
|
||||||
git clone --depth 1 --branch main --quiet \
|
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
|
||||||
/tmp/moko-platform-api 2>/dev/null || true
|
|
||||||
if [ -d "/tmp/moko-platform-api" ] && [ -f "/tmp/moko-platform-api/composer.json" ]; then
|
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check FTP configuration
|
|
||||||
id: check
|
|
||||||
env:
|
|
||||||
HOST: ${{ vars.DEV_FTP_HOST }}
|
|
||||||
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
|
|
||||||
PORT: ${{ vars.DEV_FTP_PORT }}
|
|
||||||
run: |
|
|
||||||
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
|
|
||||||
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "host=$HOST" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
REMOTE="${PATH_VAR%/}"
|
|
||||||
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
[ -z "$PORT" ] && PORT="22"
|
|
||||||
echo "port=$PORT" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Deploy via SFTP
|
|
||||||
if: steps.check.outputs.skip != 'true'
|
|
||||||
env:
|
|
||||||
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
|
||||||
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
|
||||||
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
|
||||||
run: |
|
|
||||||
SOURCE_DIR="src"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
|
|
||||||
|
|
||||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
|
||||||
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
|
|
||||||
> /tmp/sftp-config.json
|
|
||||||
|
|
||||||
if [ -n "$SFTP_KEY" ]; then
|
|
||||||
echo "$SFTP_KEY" > /tmp/deploy_key
|
|
||||||
chmod 600 /tmp/deploy_key
|
|
||||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
|
||||||
else
|
|
||||||
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
|
||||||
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
|
|
||||||
|
|
||||||
PLATFORM=$(php /tmp/moko-platform-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
|
||||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform-api/deploy/deploy-joomla.php" ]; then
|
|
||||||
php /tmp/moko-platform-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
|
||||||
else
|
|
||||||
php /tmp/moko-platform-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
|
||||||
|
|
||||||
|
|
||||||
- name: Post-deploy health check
|
|
||||||
if: success() && steps.check.outputs.skip != 'true'
|
|
||||||
run: |
|
|
||||||
if [ -f "deploy/health-check.php" ]; then
|
|
||||||
SITE_URL="${{ vars.DEV_SITE_URL }}"
|
|
||||||
if [ -n "$SITE_URL" ]; then
|
|
||||||
php deploy/health-check.php --url "$SITE_URL" --checks http --timeout 30 || echo "::warning::Health check failed after deploy"
|
|
||||||
else
|
|
||||||
echo "DEV_SITE_URL not configured, skipping health check"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
|
|
||||||
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Create branch and comment
|
- name: Create branch and comment
|
||||||
run: |
|
run: |
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||||
|
|||||||
@@ -204,11 +204,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Trigger RC pre-release
|
- name: Trigger RC pre-release
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
BRANCH: ${{ github.head_ref }}
|
BRANCH: ${{ github.head_ref }}
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
run: |
|
run: |
|
||||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
name: "Universal: Pre-Release"
|
name: "Universal: Pre-Release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
stability:
|
stability:
|
||||||
@@ -35,56 +39,44 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: "Build Pre-Release (${{ inputs.stability }})"
|
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||||
runs-on: release
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
|
||||||
- name: Setup tools
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
# Update moko-platform CLI tools if available; install PHP if missing
|
if ! command -v composer &> /dev/null; then
|
||||||
if command -v moko-platform-update &> /dev/null; 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
|
||||||
moko-platform-update
|
|
||||||
elif [ -d "/opt/moko-platform" ]; then
|
|
||||||
cd /opt/moko-platform && git pull origin main --quiet 2>/dev/null || true
|
|
||||||
else
|
|
||||||
if ! command -v php &> /dev/null; then
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
|
|
||||||
fi
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
/tmp/moko-platform-api
|
/tmp/moko-platform-api
|
||||||
fi
|
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||||
# Set MOKO_CLI to whichever path exists
|
|
||||||
if [ -d "/opt/moko-platform/cli" ]; then
|
|
||||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
|
||||||
else
|
|
||||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Detect platform
|
- name: Detect platform
|
||||||
id: platform
|
id: platform
|
||||||
run: |
|
run: |
|
||||||
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1 | tr -d '[:space:]')
|
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
|
||||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
|
||||||
MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
|
||||||
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
|
||||||
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
|
||||||
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
|
|
||||||
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Resolve metadata and bump version
|
- name: Resolve metadata and bump version
|
||||||
id: meta
|
id: meta
|
||||||
run: |
|
run: |
|
||||||
STABILITY="${{ inputs.stability }}"
|
STABILITY="${{ inputs.stability || 'development' }}"
|
||||||
|
|
||||||
case "$STABILITY" in
|
case "$STABILITY" in
|
||||||
development) SUFFIX="-dev"; TAG="development" ;;
|
development) SUFFIX="-dev"; TAG="development" ;;
|
||||||
@@ -93,60 +85,44 @@ jobs:
|
|||||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Patch bump via CLI tool
|
# Read current version (bump already handled by push workflow)
|
||||||
php ${MOKO_CLI}/version_bump.php --path .
|
|
||||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||||
TODAY=$(date +%Y-%m-%d)
|
|
||||||
|
|
||||||
# Update platform-specific manifest
|
# Strip any existing suffix from version before applying stability
|
||||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
MANIFEST="${{ steps.platform.outputs.manifest }}"
|
|
||||||
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
|
|
||||||
|
|
||||||
php ${MOKO_CLI}/version_set_platform.php \
|
php ${MOKO_CLI}/version_set_platform.php \
|
||||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
|
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Verify version consistency across all files
|
||||||
|
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
|
# Update VERSION variable with suffix
|
||||||
|
if [ -n "$SUFFIX" ]; then
|
||||||
|
VERSION="${VERSION}${SUFFIX}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Commit version bump
|
# Commit version bump
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "gitea-actions[bot]"
|
||||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
git add -A
|
git add -A
|
||||||
git diff --cached --quiet || {
|
git diff --cached --quiet || {
|
||||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||||
git push origin HEAD 2>&1
|
git push origin HEAD 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Auto-detect element (platform-aware)
|
# Auto-detect element via manifest_element.php
|
||||||
EXT_ELEMENT=""
|
php ${MOKO_CLI}/manifest_element.php \
|
||||||
case "$PLATFORM" in
|
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||||
joomla)
|
--repo "${GITEA_REPO}" --github-output
|
||||||
if [ -n "$MANIFEST" ]; then
|
|
||||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
|
||||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
|
||||||
case "$EXT_ELEMENT" in
|
|
||||||
templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
dolibarr)
|
|
||||||
if [ -n "$MOD_FILE" ]; then
|
|
||||||
MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
|
|
||||||
EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
|
|
||||||
else
|
|
||||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
|
# Read back element outputs
|
||||||
|
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
|
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
|
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||||
|
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
|
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
@@ -154,168 +130,50 @@ jobs:
|
|||||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||||
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||||
|
|
||||||
- name: Build package
|
- name: Create release
|
||||||
run: |
|
|
||||||
SOURCE_DIR="src"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
|
||||||
if [ ! -d "$SOURCE_DIR" ]; then
|
|
||||||
echo "::error::No src/ or htdocs/ directory"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MANIFEST="${{ steps.meta.outputs.manifest }}"
|
|
||||||
EXT_TYPE=""
|
|
||||||
if [ -n "$MANIFEST" ]; then
|
|
||||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
fi
|
|
||||||
|
|
||||||
EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
|
|
||||||
|
|
||||||
mkdir -p build/package
|
|
||||||
|
|
||||||
if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
|
|
||||||
echo "=== Building Joomla PACKAGE (multi-extension) ==="
|
|
||||||
for ext_dir in "${SOURCE_DIR}"/packages/*/; do
|
|
||||||
[ ! -d "$ext_dir" ] && continue
|
|
||||||
EXT_NAME=$(basename "$ext_dir")
|
|
||||||
echo " Packaging sub-extension: ${EXT_NAME}"
|
|
||||||
cd "$ext_dir"
|
|
||||||
zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES
|
|
||||||
cd "$OLDPWD"
|
|
||||||
done
|
|
||||||
for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
|
|
||||||
[ -f "$f" ] && cp "$f" build/package/
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "=== Building standard extension ==="
|
|
||||||
rsync -a \
|
|
||||||
--exclude='sftp-config*' \
|
|
||||||
--exclude='.ftpignore' \
|
|
||||||
--exclude='*.ppk' \
|
|
||||||
--exclude='*.pem' \
|
|
||||||
--exclude='*.key' \
|
|
||||||
--exclude='.env*' \
|
|
||||||
--exclude='*.local' \
|
|
||||||
--exclude='.build-trigger' \
|
|
||||||
"${SOURCE_DIR}/" build/package/
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create ZIP
|
|
||||||
id: zip
|
|
||||||
run: |
|
|
||||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
|
||||||
cd build/package
|
|
||||||
zip -r "../${ZIP_NAME}" .
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
|
|
||||||
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
|
|
||||||
|
|
||||||
- name: Create or replace Gitea release
|
|
||||||
id: release
|
id: release
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
php ${MOKO_CLI}/release_create.php \
|
||||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
BRANCH=$(git branch --show-current)
|
|
||||||
|
|
||||||
BODY="## ${VERSION} ($(date +%Y-%m-%d))
|
- name: Build package and upload
|
||||||
**Channel:** ${STABILITY}
|
id: package
|
||||||
**SHA-256:** \`${SHA256}\`"
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
# Delete existing release
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
EXISTING_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
"${API}/releases/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null)
|
php ${MOKO_CLI}/release_package.php \
|
||||||
if [ -n "$EXISTING_ID" ]; then
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
"${API}/releases/${EXISTING_ID}" 2>/dev/null || true
|
--repo "${GITEA_REPO}" --output /tmp || true
|
||||||
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
|
|
||||||
"${API}/tags/${TAG}" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create release
|
|
||||||
RELEASE_ID=$(curl -sS -X POST -H "Authorization: token ${TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/releases" \
|
|
||||||
-d "$(jq -n \
|
|
||||||
--arg tag "$TAG" \
|
|
||||||
--arg target "$BRANCH" \
|
|
||||||
--arg name "${EXT_ELEMENT} ${VERSION} (${STABILITY})" \
|
|
||||||
--arg body "$BODY" \
|
|
||||||
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: true}'
|
|
||||||
)" | jq -r '.id')
|
|
||||||
|
|
||||||
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Upload ZIP
|
|
||||||
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
|
||||||
--data-binary "@build/${ZIP_NAME}"
|
|
||||||
|
|
||||||
echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
|
|
||||||
|
|
||||||
- name: Update updates.xml
|
- name: Update updates.xml
|
||||||
if: steps.platform.outputs.platform == 'joomla'
|
if: steps.platform.outputs.platform == 'joomla'
|
||||||
run: |
|
run: |
|
||||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
|
|
||||||
if [ ! -f "updates.xml" ]; then
|
if [ ! -f "updates.xml" ]; then
|
||||||
echo "No updates.xml -- skipping"
|
echo "No updates.xml -- skipping"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Map stability to XML tag name
|
SHA_FLAG=""
|
||||||
case "$STABILITY" in
|
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||||
development) XML_TAG="development" ;;
|
|
||||||
alpha) XML_TAG="alpha" ;;
|
|
||||||
beta) XML_TAG="beta" ;;
|
|
||||||
release-candidate) XML_TAG="rc" ;;
|
|
||||||
*) XML_TAG="$STABILITY" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${TAG}/${ZIP_NAME}"
|
php ${MOKO_CLI}/updates_xml_build.php \
|
||||||
|
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||||
# Use PHP to update the channel in updates.xml
|
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||||
php -r '
|
${SHA_FLAG}
|
||||||
$xml_tag = $argv[1];
|
|
||||||
$version = $argv[2];
|
|
||||||
$sha256 = $argv[3];
|
|
||||||
$url = $argv[4];
|
|
||||||
$date = date("Y-m-d");
|
|
||||||
|
|
||||||
$content = file_get_contents("updates.xml");
|
|
||||||
$pattern = "/(<update>(?:(?!<\/update>).)*?<tag>" . preg_quote($xml_tag) . "<\/tag>.*?<\/update>)/s";
|
|
||||||
|
|
||||||
$content = preg_replace_callback($pattern, function($m) use ($version, $sha256, $url, $date) {
|
|
||||||
$block = $m[0];
|
|
||||||
$block = preg_replace("/<version>[^<]*<\/version>/", "<version>{$version}</version>", $block);
|
|
||||||
if (strpos($block, "<sha256>") !== false) {
|
|
||||||
$block = preg_replace("/<sha256>[^<]*<\/sha256>/", "<sha256>{$sha256}</sha256>", $block);
|
|
||||||
} else {
|
|
||||||
$block = str_replace("</downloads>", "</downloads>\n <sha256>{$sha256}</sha256>", $block);
|
|
||||||
}
|
|
||||||
$block = preg_replace("/(<downloadurl[^>]*>)[^<]*(<\/downloadurl>)/", "\${1}{$url}\${2}", $block);
|
|
||||||
return $block;
|
|
||||||
}, $content);
|
|
||||||
|
|
||||||
file_put_contents("updates.xml", $content);
|
|
||||||
echo "Updated {$xml_tag} channel: version={$version}\n";
|
|
||||||
' "$XML_TAG" "$VERSION" "$SHA256" "$DOWNLOAD_URL"
|
|
||||||
|
|
||||||
# Commit and push
|
# Commit and push
|
||||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||||
@@ -337,7 +195,7 @@ jobs:
|
|||||||
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||||
echo "Syncing updates.xml -> ${BRANCH}"
|
echo "Syncing updates.xml -> ${BRANCH}"
|
||||||
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||||
git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
|
git checkout "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue
|
||||||
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||||
git add updates.xml
|
git add updates.xml
|
||||||
@@ -351,7 +209,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
php ${MOKO_CLI}/release_cascade.php \
|
php ${MOKO_CLI}/release_cascade.php \
|
||||||
--stability "${{ steps.meta.outputs.stability }}" \
|
--stability "${{ steps.meta.outputs.stability }}" \
|
||||||
@@ -364,7 +222,7 @@ jobs:
|
|||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
- name: Check actor permission (admin only)
|
- name: Check actor permission (admin only)
|
||||||
id: perm
|
id: perm
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
ACTOR: ${{ github.actor }}
|
ACTOR: ${{ github.actor }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,312 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /templates/workflows/update-server.yml
|
||||||
|
# VERSION: 05.00.00
|
||||||
|
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||||
|
#
|
||||||
|
# Thin wrapper around moko-platform CLI tools.
|
||||||
|
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||||
|
#
|
||||||
|
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||||
|
|
||||||
|
name: "Update Server"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
- 'dev/**'
|
||||||
|
- 'alpha/**'
|
||||||
|
- 'beta/**'
|
||||||
|
- 'rc/**'
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'htdocs/**'
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
- 'dev/**'
|
||||||
|
- 'alpha/**'
|
||||||
|
- 'beta/**'
|
||||||
|
- 'rc/**'
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'htdocs/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
stability:
|
||||||
|
description: 'Stability tag'
|
||||||
|
required: true
|
||||||
|
default: 'development'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- development
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
- rc
|
||||||
|
- stable
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
GITEA_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 }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-xml:
|
||||||
|
name: Update Server
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
|
||||||
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; 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
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform
|
||||||
|
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 || true
|
||||||
|
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
||||||
|
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Detect platform
|
||||||
|
id: platform
|
||||||
|
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
|
|
||||||
|
- name: Resolve stability and bump version
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.ref_name }}"
|
||||||
|
|
||||||
|
# Configure git for bot pushes
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
# Auto-bump patch version
|
||||||
|
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
|
||||||
|
|
||||||
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
# Strip any existing suffix before applying stability
|
||||||
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
|
|
||||||
|
# Determine stability from branch or manual input
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
STABILITY="${{ inputs.stability }}"
|
||||||
|
elif [[ "$BRANCH" == rc/* ]]; then
|
||||||
|
STABILITY="rc"
|
||||||
|
elif [[ "$BRANCH" == beta/* ]]; then
|
||||||
|
STABILITY="beta"
|
||||||
|
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||||
|
STABILITY="alpha"
|
||||||
|
else
|
||||||
|
STABILITY="development"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Version suffix per stability stream
|
||||||
|
case "$STABILITY" in
|
||||||
|
development) SUFFIX="-dev"; TAG="development" ;;
|
||||||
|
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||||
|
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||||
|
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||||
|
*) SUFFIX=""; TAG="stable" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Propagate version with stability suffix to all manifest files
|
||||||
|
php ${MOKO_CLI}/version_set_platform.php \
|
||||||
|
--path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
|
||||||
|
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
|
# Re-read version (now includes suffix from version_set_platform)
|
||||||
|
if [ -n "$SUFFIX" ]; then
|
||||||
|
VERSION="${VERSION}${SUFFIX}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Commit version bump if changed
|
||||||
|
git add -A
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create release and upload package
|
||||||
|
id: package
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
# Create or update Gitea release
|
||||||
|
php ${MOKO_CLI}/release_create.php \
|
||||||
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||||
|
|
||||||
|
# Build package and upload
|
||||||
|
php ${MOKO_CLI}/release_package.php \
|
||||||
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --output /tmp || true
|
||||||
|
|
||||||
|
- name: Update updates.xml
|
||||||
|
if: steps.platform.outputs.platform == 'joomla'
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
|
|
||||||
|
if [ ! -f "updates.xml" ]; then
|
||||||
|
echo "No updates.xml — skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHA_FLAG=""
|
||||||
|
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||||
|
|
||||||
|
php ${MOKO_CLI}/updates_xml_build.php \
|
||||||
|
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||||
|
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||||
|
${SHA_FLAG}
|
||||||
|
|
||||||
|
# Commit and push updates.xml
|
||||||
|
git add updates.xml
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Sync updates.xml to main
|
||||||
|
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||||
|
python3 -c "
|
||||||
|
import base64, json, urllib.request, sys
|
||||||
|
with open('updates.xml', 'rb') as f:
|
||||||
|
content = base64.b64encode(f.read()).decode()
|
||||||
|
payload = json.dumps({
|
||||||
|
'content': content,
|
||||||
|
'sha': '${FILE_SHA}',
|
||||||
|
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
|
||||||
|
'branch': 'main'
|
||||||
|
}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'${API_BASE}/contents/updates.xml',
|
||||||
|
data=payload, method='PUT',
|
||||||
|
headers={
|
||||||
|
'Authorization': 'token ${GITEA_TOKEN}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
urllib.request.urlopen(req)
|
||||||
|
print('updates.xml synced to main')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: SFTP deploy to dev server
|
||||||
|
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||||
|
env:
|
||||||
|
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||||
|
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||||
|
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||||
|
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||||
|
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||||
|
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||||
|
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
# Permission check: admin or maintain role required
|
||||||
|
ACTOR="${{ github.actor }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||||
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||||
|
case "$PERMISSION" in
|
||||||
|
admin|maintain|write) ;;
|
||||||
|
*)
|
||||||
|
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||||
|
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||||
|
|
||||||
|
PORT="${DEV_PORT:-22}"
|
||||||
|
REMOTE="${DEV_PATH%/}"
|
||||||
|
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||||
|
|
||||||
|
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||||
|
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||||
|
if [ -n "$DEV_KEY" ]; then
|
||||||
|
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||||
|
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||||
|
else
|
||||||
|
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||||
|
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||||
|
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
|
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||||
|
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
|
fi
|
||||||
|
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||||
|
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
DISPLAY="${{ steps.meta.outputs.display_version }}"
|
||||||
|
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
+72
-141
@@ -10,163 +10,94 @@ BRIEF: Release changelog
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [09.04.00] --- 2026-05-28
|
||||||
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
Version format: `XX.YY.ZZ` (zero-padded semver).
|
Version format: `XX.YY.ZZ` (zero-padded semver).
|
||||||
|
|
||||||
## [Unreleased]
|
## [09.03.00] --- 2026-05-28
|
||||||
|
|
||||||
## [07.00.00] - 2026-05-25
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `cli/client_provision.php` — end-to-end client onboarding from JSON config (closes #4)
|
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge — synced to all 47 repos
|
||||||
- `cli/client_dashboard.php` — unified HTML dashboard: health, SSL, uptime, releases (closes #3)
|
- `governance.yml`: lightweight YAML schema replacing HCL definition files for repo governance config
|
||||||
- `cli/client_health_check.php`, `cli/joomla_compat_check.php`, `cli/theme_lint.php` — new CLI tools
|
- `auto-bump.yml`: auto patch-bump version on every push to dev
|
||||||
- `lib/Enterprise/ConfigValidator.php` — JSON schema validator for plugin configs (closes #105)
|
|
||||||
- PHPUnit test infrastructure: `phpunit.xml` + 19 tests (closes #102)
|
### Changed
|
||||||
- `bin/moko list` — auto-grouped command list with 45 commands, plugin command dispatcher (closes #104)
|
- **Definitions removed**: deleted `definitions/` directory (63,602 lines of HCL) — Template repos are now the canonical source for platform-specific files
|
||||||
- `templates/client-provision-example.json` — example config for client provisioning
|
- **Template path migration**: `templates/gitea/` → `templates/mokogitea/`, all `.github/` references → `.mokogitea/` across definitions and sync tools
|
||||||
|
- `version_bump.php`: preserves version suffix (e.g. `-dev`) through bumps — moko manifest is now the single source of truth for the full version string (#191)
|
||||||
|
- `version_read.php`: accepts suffix from moko manifest (was stripping it)
|
||||||
|
- `update-server.yml`: removed `DISPLAY_VERSION` — derives filename directly from manifest version (#191)
|
||||||
|
- `pre-release.yml`: removed `SUFFIX` variable — version string already includes suffix
|
||||||
|
- `push_files.php`: detects platform from manifest.xml via API instead of local sync definition files
|
||||||
|
- `auto_detect_platform.php`: gracefully handles missing schema directory
|
||||||
|
- `DefinitionParser.php`: deleted — no longer needed
|
||||||
|
- `manifest-schema.xsd`: moved from `definitions/` to `templates/schemas/`
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `definitions/default/` — 10 HCL definition files (generic, joomla, dolibarr, platform, standards, etc.)
|
||||||
|
- `definitions/sync/` — 48 auto-generated sync tracking files
|
||||||
|
- `lib/Enterprise/DefinitionParser.php` — HCL parser (replaced by Template repo sourcing)
|
||||||
|
- Redundant bump from pre-release.yml (handled by auto-bump)
|
||||||
|
- 47 merged feature branches cleaned up from remote
|
||||||
|
|
||||||
|
## [09.02.00] - 2026-05-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Release promotion pipeline**: draft PR → RC promotion, merged PR → RC-to-stable (skip rebuild)
|
||||||
|
- **7 new CLI tools**: `manifest_element.php`, `release_create.php`, `release_package.php`, `release_promote.php`, `release_mirror.php`, `version_reset_dev.php`, `ManifestReader.php`
|
||||||
|
- `version_bump.php` / `version_read.php`: support for `package.json` (Node.js) and `pyproject.toml` (Python)
|
||||||
|
- `version_bump.php`: now writes bumped version to all sources (README, manifests, Dolibarr mod, composer.json, package.json, pyproject.toml)
|
||||||
|
- `release_cascade.php`: `--version` flag for version-aware deletion of stale releases
|
||||||
|
- `release_validate.php`: auto-detect platform from manifest.xml, `--github-output` flag, source dir check
|
||||||
|
- `updates_xml_build.php`: supports non-Joomla platforms via manifest.xml detection
|
||||||
|
- `release_package.php`: reads entry-point from manifest.xml for source dir resolution
|
||||||
|
- `auto-release.yml`: `workflow_dispatch` with `promote-rc` action as fallback for MokoGitea#220
|
||||||
|
- `update-server.yml`: now universal — pushed to all 69+ repos (Joomla, Dolibarr, generic, MCP)
|
||||||
|
- `ManifestReader.php`: shared typed accessor for `.mokogitea/manifest.xml`
|
||||||
|
- Universal workflow cascade: Template-Generic → other templates → all repos via `bulk_sync.php`
|
||||||
|
- Wiki: UPDATE_SERVER standard page on moko-platform and all template repos
|
||||||
|
- PHPDoc added to 4 classes missing class-level docs
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `auto-release.yml`: 761 → 490 lines — replaced all inline bash with CLI tool calls
|
||||||
|
- `pre-release.yml`: 389 → 314 lines — replaced inline logic with `manifest_read.php`, `manifest_element.php`, `updates_xml_build.php`
|
||||||
|
- Removed `paths` filter from workflow triggers (enables Go, Node.js, generic repo compatibility)
|
||||||
|
- `RepositorySynchronizer.php`: fixed template repo names, `.mokogitea/workflows` path, universal workflow sync
|
||||||
|
- Template-Generic is now the single source of truth for universal workflows
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- `bin/moko` COMMAND_MAP: all paths pointed to non-existent `api/` directory (closes #100)
|
- `release_cascade.php` in `auto-release.yml`: was using `--org`/`--repo` flags instead of `--api-base`
|
||||||
- `release_cascade.php`: accept `release-candidate` as stability value (was silently skipping)
|
- `pre-release.yml`: updates.xml sync was checking out entire branch tree instead of just `updates.xml`
|
||||||
- `package_build.php`: fix 0-byte ZIP for Joomla packages — correct structure, no double prefix (closes #92)
|
- MokoWaaS#48: Joomla 6 typed event API fix for `plg_webservices_mokowaas`
|
||||||
- PHPStan: level 0 to 2, 67 type errors fixed, 0 exclusions
|
|
||||||
- `ApiClient::delete()`: accept optional body parameter for Gitea Contents API
|
|
||||||
|
|
||||||
### Changed
|
## [09.00.00] - 2026-05-26
|
||||||
- Migrated all 7 CLIApp scripts to CliFramework (closes #101)
|
|
||||||
- Updated CLAUDE.md with current architecture, CLI patterns, code quality (closes #103)
|
|
||||||
- Wiki CLI_AUTOMATION page updated with all tools
|
|
||||||
|
|
||||||
## [06.00.00] - 2026-05-25
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `cli/bulk_workflow_push.php` — push a workflow file to all governed repos via Gitea Contents API (closes #52)
|
- PHPDoc on Priority 1 Enterprise classes (CliFramework, adapters, ApiClient)
|
||||||
- `cli/grafana_dashboard.php` — manage Grafana dashboards: push, delete, list, export (closes #53)
|
- Wiki: Coding-Standards page with PHPDoc standard, PHPCS exclusions, file patterns
|
||||||
- Wiki CLI_AUTOMATION page — comprehensive reference for all 30 CLI tools (closes #66)
|
- CI: PHPStan enforced at level 6 (was advisory), PHPUnit blocks on failure
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- `version_read.php` / `version_bump.php`: handle suffixed versions in XML manifests (e.g. `01.00.00-dev`)
|
- `updates_xml_build.php`: cascade entries down to lower channels — stable now writes all 5 entries instead of wiping them
|
||||||
- `version_read.php` / `version_bump.php`: match `VERSION:` inside HTML comments (`<!-- VERSION: ... -->`)
|
- `updates_xml_build.php`: separate Joomla stability tags (`dev`, `rc`) from Gitea release tags (`development`, `release-candidate`) — download URLs now point to correct release assets
|
||||||
- Pre-release RC builds now work after a development pre-release has been built
|
- `updates_xml_build.php`: only emit `<client>site</client>` for templates and modules, not packages or components
|
||||||
- auto-release workflow: switch trigger from `pull_request closed` to `push` on main (closes #54)
|
- `updates_xml_build.php`: preservation logic matches Joomla tag names when deciding which existing entries to keep
|
||||||
- CI Gate 1: add ondrej/php PPA + composer package for PHP 8.2 on runners
|
|
||||||
- CI repo-health: use `.mokogitea/workflows/` instead of `.gitea/workflows/`
|
## [08.00.00] - 2026-05-26
|
||||||
- PHPCS: fix all 7,539 PSR-12 violations across 74 files (0 errors remaining)
|
|
||||||
- PHPStan: fix deprecated config options, mark as advisory until errors addressed
|
|
||||||
- Branch protection: update check names from `MokoStandards CI` to `moko-platform CI`
|
|
||||||
- Runner-03: fix Docker image label (`moko/runner-images` → self-hosted `git.mokoconsulting.tech/mokoconsulting/runner-image`)
|
|
||||||
- Runbook 08: update with 3-runner fleet overview, per-runner configs, troubleshooting
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Rename MokoStandards references to moko-platform in config files
|
- PHPStan: level 5 → 6 (401 baselined, 0 new errors)
|
||||||
|
- Branch protection: 5 required checks enabled on main
|
||||||
## [05.00.00] - 2026-05-16
|
- Workflows synced to all governed repos (72+ repos across 3 orgs)
|
||||||
|
- Flushed 44 stale runners from Gitea admin (3 active remain)
|
||||||
### Added
|
|
||||||
- `server-autoheal.sh` — boot-check, split system/content backups, self-installing with cron + systemd hook
|
|
||||||
- Grafana library panels: legend (list, right) and multi-tooltip options on all 14 panels
|
|
||||||
- Prometheus targets volume mount in monitoring Docker Compose
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- MokoWaaS dashboard: remove `v_hidden` column — use explicit `filterFieldsByName` regex instead of broken `excludeByName`
|
- PHPStan level 3→4: removed 13 dead properties, 41 defensive patterns baselined
|
||||||
- MokoWaaS dashboard: simplify probe queries (remove redundant `and on(site_name)` joins)
|
- PHPStan level 4→5: fixed metrics `increment()` bug (labels passed as value param)
|
||||||
|
- PHPStan level 5→6: 360 missing array generic types baselined
|
||||||
### Changed
|
|
||||||
- Rename `gitea-server-setup` → `.mokogitea-private` in workflow EXCLUDE lists
|
|
||||||
- Dolibarr Module ID Registry moved to MokoDolibarr wiki (moko-platform page is now a redirect)
|
|
||||||
|
|
||||||
## [04.09.00] - 2026-05-12
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- `<deploy>` section support in `.manifest.xml` schema: `source-dir`, `remote-subdir`, `excludes`, `dev-host`, `demo-host`
|
|
||||||
- `manifest_read.php` now parses all deploy fields for CI consumption
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Deploy workflows can now read deploy paths from manifest instead of guessing from directory structure
|
|
||||||
|
|
||||||
## [04.08.00] - 2026-05-12
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- `cli/manifest_read.php` -- full `.manifest.xml` parser for CI consumption
|
|
||||||
- Supports `--field`, `--all`, `--json`, and `--github-output` modes
|
|
||||||
- Backward-compatible with `.moko-platform` (XML) and `.mokostandards` (YAML) formats
|
|
||||||
- Replaces inline `sed` detection blocks in workflows
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Workflows (`auto-release`, `pre-release`, `pr-check`) now use `manifest_read.php` for platform detection
|
|
||||||
- `entry-point` field from manifest replaces `find` tree scan for mod file discovery
|
|
||||||
- Platform detection outputs all manifest fields to `GITHUB_OUTPUT` (name, org, language, package-type, etc.)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [05.00.00] - 2026-05-11
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Centralized MokoWaaS Grafana dashboard for all Joomla sites (2-column layout)
|
|
||||||
- MokoStandards MCP server with 24 governance tools
|
|
||||||
- Wiki health check and GitHub wiki mirror sync
|
|
||||||
- Daily wiki sync workflow — mirrors all Gitea wikis to GitHub
|
|
||||||
- CHANGELOG `[Unreleased]` section check in repo health (5 pts)
|
|
||||||
- Client platform type with detection and structure definition
|
|
||||||
- PHPStan, Gitleaks, and Renovate — templates, workflows, and docs
|
|
||||||
- Cascade and branch protection workflow documentation
|
|
||||||
- Branch protection setup workflow
|
|
||||||
- Client-site definition
|
|
||||||
- Pre-release workflow for manual dev/alpha/beta/rc builds
|
|
||||||
- PR-check, security-audit, notify, cleanup workflow definitions
|
|
||||||
- Expanded workflow suite (10 workflows from MokoOnyx)
|
|
||||||
- `.gitea/workflows` definitions to Joomla structure defs
|
|
||||||
- Joomla workflow templates from MokoOnyx
|
|
||||||
- Cleanup script to remove `.claude/` and `.mcp.json` from repos
|
|
||||||
- Auto-discover all repos with wikis across all orgs
|
|
||||||
- CLAUDE.md to repo health check, flag unwanted files
|
|
||||||
- `.moko-platform` manifest (replaces `.mokostandards`)
|
|
||||||
- PR branch policy check workflow
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Major version bump: `04.05.00` → `05.00.00` across all definitions, templates, and wiki
|
|
||||||
- Grafana endpoint dashboards: 2 columns per row (reduced congestion)
|
|
||||||
- Sync engine clones template repos at runtime for workflows
|
|
||||||
- Simplified platform types across definitions and sync engine
|
|
||||||
- Removed `templates/github` — all CI/templates now in `.gitea/`
|
|
||||||
- Removed `templates/workflows` — canonical source is now template repos
|
|
||||||
- Updated mokostandards xmlns to point to MokoStandards-API repo
|
|
||||||
- Comprehensive repo health check updates
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Remove gitea-actions[bot] from push whitelist (not a real user)
|
|
||||||
- Delete-then-create branch protection rules to avoid 422
|
|
||||||
- Patch version bump in pre-release workflow
|
|
||||||
- Always emit `<client>` tag in UpdateXmlGenerator
|
|
||||||
- Rewrite `updates.xml.template` with 5 stability channels
|
|
||||||
- Migrate `.mokostandards` from `.github/` to `.gitea/` on Gitea
|
|
||||||
|
|
||||||
## [04.05.00] - 2026-03-15
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Dual-platform support (Gitea + GitHub) and Joomla template tooling
|
|
||||||
- Templates, CLI dirs, docs, and Gitea-first platform config
|
|
||||||
- Sync to all branches, listBranches, ext-zip
|
|
||||||
- All templates from MokoStandards
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Migrated to Gitea-only workflows and API
|
|
||||||
- Converted all gh CLI calls to Gitea API curl across workflow templates
|
|
||||||
- Gitea-primary tokens: GA_TOKEN for Gitea API, GH_TOKEN for GitHub mirror
|
|
||||||
- Updated all references to MokoConsulting org and Gitea URLs
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Guzzle base_uri resolution for Gitea API paths
|
|
||||||
- Replace all hardcoded GitHub API URLs with platform adapter pattern
|
|
||||||
- Split repoRoot into apiRoot + standardsRoot
|
|
||||||
- Auto-release template: use Gitea API for main sync, auth push URL
|
|
||||||
- Bulk_sync: resolve label names to IDs, fix username
|
|
||||||
- Remove sha256: prefix from update XML templates
|
|
||||||
|
|
||||||
## [04.00.00] - 2026-01-01
|
|
||||||
|
|
||||||
- Initial release: MokoStandards Enterprise API extracted from MokoStandards
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ This file provides guidance to Claude Code when working with this repository.
|
|||||||
| **Language** | PHP 8.1+ |
|
| **Language** | PHP 8.1+ |
|
||||||
| **Default branch** | main |
|
| **Default branch** | main |
|
||||||
| **License** | GPL-3.0-or-later |
|
| **License** | GPL-3.0-or-later |
|
||||||
| **Version** | 06.00.00 |
|
| **Version** | 09.01.00 |
|
||||||
| **Wiki** | [moko-platform Wiki](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki) |
|
| **Wiki** | [moko-platform Wiki](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki) |
|
||||||
|
|
||||||
## Common Commands
|
## Common Commands
|
||||||
@@ -44,8 +44,7 @@ composer check
|
|||||||
| `lib/Enterprise/` | Core library — CliFramework, ApiClient, adapters, validators |
|
| `lib/Enterprise/` | Core library — CliFramework, ApiClient, adapters, validators |
|
||||||
| `lib/Enterprise/Plugins/` | 11 platform plugins (Joomla, Dolibarr, Node.js, Python, etc.) |
|
| `lib/Enterprise/Plugins/` | 11 platform plugins (Joomla, Dolibarr, Node.js, Python, etc.) |
|
||||||
| `deploy/` | SFTP deployment scripts (Joomla, Dolibarr, health checks) |
|
| `deploy/` | SFTP deployment scripts (Joomla, Dolibarr, health checks) |
|
||||||
| `definitions/` | Repository structure definitions (HCL format) |
|
| `templates/` | Universal templates, configs, governance schema |
|
||||||
| `templates/` | Workflow templates, config templates, docs templates |
|
|
||||||
| `.mokogitea/workflows/` | CI/CD workflows (Gitea Actions) |
|
| `.mokogitea/workflows/` | CI/CD workflows (Gitea Actions) |
|
||||||
| `bin/moko` | Unified CLI dispatcher — runs any tool via `php bin/moko <command>` |
|
| `bin/moko` | Unified CLI dispatcher — runs any tool via `php bin/moko <command>` |
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ DEFGROUP: MokoStandards.Root
|
|||||||
INGROUP: MokoStandards
|
INGROUP: MokoStandards
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
PATH: /README.md
|
PATH: /README.md
|
||||||
|
VERSION: 09.03.03
|
||||||
BRIEF: Project overview and documentation
|
BRIEF: Project overview and documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# MokoStandards Enterprise API
|
# MokoStandards Enterprise API
|
||||||
|
|
||||||
|
  
|
||||||
|
|
||||||
PHP implementation of MokoStandards — enterprise standards, automation framework, workflow templates, and bulk sync tooling.
|
PHP implementation of MokoStandards — enterprise standards, automation framework, workflow templates, and bulk sync tooling.
|
||||||
|
|
||||||
> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API)
|
> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API)
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ class BulkJoomlaTemplate extends CliFramework
|
|||||||
public const VERSION = '04.06.10';
|
public const VERSION = '04.06.10';
|
||||||
|
|
||||||
private GitPlatformAdapter $adapter;
|
private GitPlatformAdapter $adapter;
|
||||||
private AuditLogger $logger;
|
|
||||||
private Config $config;
|
private Config $config;
|
||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
@@ -85,7 +84,6 @@ class BulkJoomlaTemplate extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger = new AuditLogger('joomla_template');
|
|
||||||
$org = $this->getArgument('--org', self::DEFAULT_ORG);
|
$org = $this->getArgument('--org', self::DEFAULT_ORG);
|
||||||
$platform = $this->adapter->getPlatformName();
|
$platform = $this->adapter->getPlatformName();
|
||||||
$this->log("Platform: {$platform} | Organization: {$org}", 'INFO');
|
$this->log("Platform: {$platform} | Organization: {$org}", 'INFO');
|
||||||
|
|||||||
@@ -66,9 +66,6 @@ class BulkSync extends CliFramework
|
|||||||
private AuditLogger $logger;
|
private AuditLogger $logger;
|
||||||
private CheckpointManager $checkpoints;
|
private CheckpointManager $checkpoints;
|
||||||
private MetricsCollector $metrics;
|
private MetricsCollector $metrics;
|
||||||
private SecurityValidator $security;
|
|
||||||
private PluginFactory $pluginFactory;
|
|
||||||
private ProjectTypeDetector $typeDetector;
|
|
||||||
private Config $config;
|
private Config $config;
|
||||||
|
|
||||||
/** Set to true by signal handler or rate-limit detection to abort the sync loop gracefully. */
|
/** Set to true by signal handler or rate-limit detection to abort the sync loop gracefully. */
|
||||||
@@ -159,6 +156,11 @@ class BulkSync extends CliFramework
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync universal workflows from Template-Generic → other templates first
|
||||||
|
$this->log("📋 Syncing universal workflows to template repos...", 'INFO');
|
||||||
|
$templateUpdates = $this->synchronizer->syncUniversalWorkflowsToTemplates($org);
|
||||||
|
$this->log("Template sync: {$templateUpdates} file(s) updated", 'INFO');
|
||||||
|
|
||||||
// Execute synchronization
|
// Execute synchronization
|
||||||
$this->log("🔄 Starting synchronization...", 'INFO');
|
$this->log("🔄 Starting synchronization...", 'INFO');
|
||||||
$results = $this->executeSynchronization($org, $repositories, $alreadyProcessed);
|
$results = $this->executeSynchronization($org, $repositories, $alreadyProcessed);
|
||||||
@@ -204,7 +206,6 @@ class BulkSync extends CliFramework
|
|||||||
$this->logger = new AuditLogger('bulk_sync');
|
$this->logger = new AuditLogger('bulk_sync');
|
||||||
$this->metrics = new MetricsCollector();
|
$this->metrics = new MetricsCollector();
|
||||||
$this->checkpoints = new CheckpointManager('.checkpoints');
|
$this->checkpoints = new CheckpointManager('.checkpoints');
|
||||||
$this->security = new SecurityValidator();
|
|
||||||
$this->synchronizer = new RepositorySynchronizer(
|
$this->synchronizer = new RepositorySynchronizer(
|
||||||
$this->api,
|
$this->api,
|
||||||
$this->logger,
|
$this->logger,
|
||||||
@@ -215,8 +216,6 @@ class BulkSync extends CliFramework
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Initialize plugin system
|
// Initialize plugin system
|
||||||
$this->pluginFactory = new PluginFactory($this->logger, $this->metrics);
|
|
||||||
$this->typeDetector = new ProjectTypeDetector($this->logger);
|
|
||||||
|
|
||||||
$this->log("✓ Enterprise components initialized for platform: {$platform}", 'INFO');
|
$this->log("✓ Enterprise components initialized for platform: {$platform}", 'INFO');
|
||||||
return true;
|
return true;
|
||||||
@@ -288,7 +287,7 @@ class BulkSync extends CliFramework
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values(array_merge($priority, $rest));
|
return array_merge($priority, $rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+20
-38
@@ -26,7 +26,6 @@ use MokoEnterprise\{
|
|||||||
AuditLogger,
|
AuditLogger,
|
||||||
CliFramework,
|
CliFramework,
|
||||||
Config,
|
Config,
|
||||||
DefinitionParser,
|
|
||||||
GitPlatformAdapter,
|
GitPlatformAdapter,
|
||||||
MetricsCollector,
|
MetricsCollector,
|
||||||
PlatformAdapterFactory,
|
PlatformAdapterFactory,
|
||||||
@@ -59,7 +58,6 @@ class PushFiles extends CliFramework
|
|||||||
private ApiClient $api;
|
private ApiClient $api;
|
||||||
private GitPlatformAdapter $adapter;
|
private GitPlatformAdapter $adapter;
|
||||||
private AuditLogger $logger;
|
private AuditLogger $logger;
|
||||||
private DefinitionParser $defParser;
|
|
||||||
private ProjectTypeDetector $typeDetector;
|
private ProjectTypeDetector $typeDetector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +152,6 @@ class PushFiles extends CliFramework
|
|||||||
$this->adapter = PlatformAdapterFactory::create($config);
|
$this->adapter = PlatformAdapterFactory::create($config);
|
||||||
$this->api = $this->adapter->getApiClient();
|
$this->api = $this->adapter->getApiClient();
|
||||||
$this->logger = new AuditLogger('push_files');
|
$this->logger = new AuditLogger('push_files');
|
||||||
$this->defParser = new DefinitionParser();
|
|
||||||
$this->typeDetector = new ProjectTypeDetector($this->logger);
|
$this->typeDetector = new ProjectTypeDetector($this->logger);
|
||||||
|
|
||||||
$platform = $this->adapter->getPlatformName();
|
$platform = $this->adapter->getPlatformName();
|
||||||
@@ -198,43 +195,24 @@ class PushFiles extends CliFramework
|
|||||||
$platform = $this->detectRepoPlatform($org, $repo);
|
$platform = $this->detectRepoPlatform($org, $repo);
|
||||||
$this->log(" {$repo}: platform = {$platform}", 'INFO');
|
$this->log(" {$repo}: platform = {$platform}", 'INFO');
|
||||||
|
|
||||||
// Build a destination→source lookup from the definition
|
|
||||||
$defEntries = $this->defParser->parseForPlatform($platform, $repoRoot);
|
|
||||||
$destToSource = [];
|
|
||||||
foreach ($defEntries as $entry) {
|
|
||||||
$destToSource[$entry['destination']] = $entry['source'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$resolved = [];
|
$resolved = [];
|
||||||
foreach ($files as $fileSpec) {
|
foreach ($files as $fileSpec) {
|
||||||
if (str_contains($fileSpec, ':')) {
|
if (str_contains($fileSpec, ':')) {
|
||||||
// Raw source:destination pair
|
// Raw source:destination pair
|
||||||
[$src, $dest] = explode(':', $fileSpec, 2);
|
[$src, $dest] = explode(':', $fileSpec, 2);
|
||||||
|
} else {
|
||||||
|
// Same path as source and destination
|
||||||
|
$src = $fileSpec;
|
||||||
|
$dest = $fileSpec;
|
||||||
|
}
|
||||||
|
$dest = ltrim($dest, '/');
|
||||||
$srcAbs = rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
|
$srcAbs = rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
|
||||||
if (!file_exists($srcAbs)) {
|
if (!file_exists($srcAbs)) {
|
||||||
$this->log(" ⚠️ Source not found for {$repo}: {$src}", 'WARN');
|
$this->log(" ⚠️ Source not found for {$repo}: {$src}", 'WARN');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
|
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
|
||||||
$this->log(" ✓ {$dest} (raw: {$src})", 'INFO');
|
|
||||||
} else {
|
|
||||||
// Destination path — look up in definition
|
|
||||||
$dest = ltrim($fileSpec, '/');
|
|
||||||
if (isset($destToSource[$dest])) {
|
|
||||||
$src = $destToSource[$dest];
|
|
||||||
$srcAbs = str_starts_with($src, '/')
|
|
||||||
? $src
|
|
||||||
: rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
|
|
||||||
if (!file_exists($srcAbs)) {
|
|
||||||
$this->log(" ⚠️ Template not found for {$repo}: {$src}", 'WARN');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
|
|
||||||
$this->log(" ✓ {$dest}", 'INFO');
|
$this->log(" ✓ {$dest}", 'INFO');
|
||||||
} else {
|
|
||||||
$this->log(" ⚠️ {$dest} not found in {$platform} definition for {$repo}", 'WARN');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($resolved)) {
|
if (!empty($resolved)) {
|
||||||
@@ -246,24 +224,28 @@ class PushFiles extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect platform for a repo by checking its sync def file, falling back
|
* Detect platform for a repo via manifest or live detection.
|
||||||
* to the live GitHub API detection used by bulk_sync.
|
|
||||||
*/
|
*/
|
||||||
private function detectRepoPlatform(string $org, string $repo): string
|
private function detectRepoPlatform(string $org, string $repo): string
|
||||||
{
|
{
|
||||||
// Check local sync def first — fastest path
|
// Read platform from repo's .mokogitea/manifest.xml via API
|
||||||
$defDir = dirname(__DIR__) . '/definitions/sync';
|
try {
|
||||||
$defFile = "{$defDir}/{$repo}.def.tf";
|
$manifestData = $this->adapter->getFileContent($org, $repo, '.mokogitea/manifest.xml', 'main');
|
||||||
if (file_exists($defFile)) {
|
if (!empty($manifestData)) {
|
||||||
$content = file_get_contents($defFile) ?: '';
|
$xml = @simplexml_load_string($manifestData);
|
||||||
if (preg_match('/detected_platform\s*=\s*"([^"]+)"/', $content, $m)) {
|
if ($xml !== false) {
|
||||||
return $m[1];
|
$platform = (string)($xml->governance->platform ?? '');
|
||||||
|
if (!empty($platform)) {
|
||||||
|
return $platform;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Fall through to local detection
|
||||||
|
}
|
||||||
|
|
||||||
// Fall back to live detection
|
// Fall back to live detection
|
||||||
try {
|
try {
|
||||||
$repoData = $this->api->get("/repos/{$org}/{$repo}");
|
|
||||||
$result = $this->typeDetector->detect('.');
|
$result = $this->typeDetector->detect('.');
|
||||||
return $result['type'] ?? 'default';
|
return $result['type'] ?? 'default';
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ class RepoCleanup extends CliFramework
|
|||||||
|
|
||||||
private ApiClient $api;
|
private ApiClient $api;
|
||||||
private GitPlatformAdapter $adapter;
|
private GitPlatformAdapter $adapter;
|
||||||
private AuditLogger $logger;
|
|
||||||
private MetricsCollector $metrics;
|
|
||||||
protected bool $dryRun = false;
|
protected bool $dryRun = false;
|
||||||
private float $startTime;
|
private float $startTime;
|
||||||
|
|
||||||
@@ -99,8 +97,6 @@ class RepoCleanup extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger = new AuditLogger('repo_cleanup');
|
|
||||||
$this->metrics = new MetricsCollector('repo_cleanup');
|
|
||||||
|
|
||||||
$this->logMsg("🧹 MokoStandards Repository Cleanup v" . self::VERSION);
|
$this->logMsg("🧹 MokoStandards Repository Cleanup v" . self::VERSION);
|
||||||
$this->logMsg("Organization: {$org}");
|
$this->logMsg("Organization: {$org}");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
*
|
*
|
||||||
@@ -123,14 +124,25 @@ const COMMAND_MAP = [
|
|||||||
'release' => 'cli/release.php',
|
'release' => 'cli/release.php',
|
||||||
'release:notes' => 'cli/release_notes.php',
|
'release:notes' => 'cli/release_notes.php',
|
||||||
'release:validate' => 'cli/release_validate.php',
|
'release:validate' => 'cli/release_validate.php',
|
||||||
|
'manifest:element' => 'cli/manifest_element.php',
|
||||||
'release:cascade' => 'cli/release_cascade.php',
|
'release:cascade' => 'cli/release_cascade.php',
|
||||||
|
'release:promote' => 'cli/release_promote.php',
|
||||||
|
'release:create' => 'cli/release_create.php',
|
||||||
'release:manage' => 'cli/release_manage.php',
|
'release:manage' => 'cli/release_manage.php',
|
||||||
|
'release:mirror' => 'cli/release_mirror.php',
|
||||||
|
'release:package' => 'cli/release_package.php',
|
||||||
|
|
||||||
|
// Changelog
|
||||||
|
'changelog:promote' => 'cli/changelog_promote.php',
|
||||||
|
'changelog:prune' => 'cli/changelog_prune.php',
|
||||||
|
|
||||||
// Version management
|
// Version management
|
||||||
'version:read' => 'cli/version_read.php',
|
'version:read' => 'cli/version_read.php',
|
||||||
'version:bump' => 'cli/version_bump.php',
|
'version:bump' => 'cli/version_bump.php',
|
||||||
|
'version:check' => 'cli/version_check.php',
|
||||||
'version:propagate' => 'maintenance/update_version_from_readme.php',
|
'version:propagate' => 'maintenance/update_version_from_readme.php',
|
||||||
'version:set-platform' => 'cli/version_set_platform.php',
|
'version:set-platform' => 'cli/version_set_platform.php',
|
||||||
|
'version:reset-dev' => 'cli/version_reset_dev.php',
|
||||||
|
|
||||||
// Build & package
|
// Build & package
|
||||||
'build:package' => 'cli/package_build.php',
|
'build:package' => 'cli/package_build.php',
|
||||||
|
|||||||
+1
-13
@@ -118,19 +118,7 @@ if (!$dryRun) {
|
|||||||
echo " (dry-run) would archive {$org}/{$repoName}\n";
|
echo " (dry-run) would archive {$org}/{$repoName}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 5: Remove sync definition ──────────────────────────────────────
|
// ── Step 5: (removed — sync definitions no longer used) ─────────────────
|
||||||
echo "Step 5: Removing sync definition...\n";
|
|
||||||
$defFile = "{$repoRoot}/definitions/sync/{$repoName}.def.tf";
|
|
||||||
if (file_exists($defFile)) {
|
|
||||||
if (!$dryRun) {
|
|
||||||
unlink($defFile);
|
|
||||||
echo " Removed: {$defFile}\n";
|
|
||||||
} else {
|
|
||||||
echo " (dry-run) would remove {$defFile}\n";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo " No sync definition found\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Step 6: Create archival record ──────────────────────────────────────
|
// ── Step 6: Create archival record ──────────────────────────────────────
|
||||||
echo "Step 6: Creating archival record...\n";
|
echo "Step 6: Creating archival record...\n";
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/changelog_prune.php
|
||||||
|
* BRIEF: Prune old CHANGELOG.md entries — keeps [Unreleased] + last N releases
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php changelog_prune.php --path /repo --keep 5
|
||||||
|
* php changelog_prune.php --path /repo --keep 3 --dry-run
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$path = '.';
|
||||||
|
$keep = 5;
|
||||||
|
$dryRun = false;
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
||||||
|
if ($arg === '--keep' && isset($argv[$i + 1])) $keep = (int)$argv[$i + 1];
|
||||||
|
if ($arg === '--dry-run') $dryRun = true;
|
||||||
|
if ($arg === '--help') {
|
||||||
|
echo "changelog_prune — Keep [Unreleased] + last N versioned entries\n\n";
|
||||||
|
echo "Usage: php changelog_prune.php --path . --keep 5 [--dry-run]\n\n";
|
||||||
|
echo "Options:\n";
|
||||||
|
echo " --path Repository path (default: .)\n";
|
||||||
|
echo " --keep Number of versioned releases to keep (default: 5)\n";
|
||||||
|
echo " --dry-run Preview without writing\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$changelog = realpath($path) . '/CHANGELOG.md';
|
||||||
|
if (!file_exists($changelog)) {
|
||||||
|
fwrite(STDERR, "No CHANGELOG.md found at {$path}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($changelog);
|
||||||
|
$lines = explode("\n", $content);
|
||||||
|
|
||||||
|
// Split into sections by ## headings
|
||||||
|
$sections = [];
|
||||||
|
$current = [];
|
||||||
|
$currentHeading = null;
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (preg_match('/^## /', $line)) {
|
||||||
|
if ($currentHeading !== null) {
|
||||||
|
$sections[] = ['heading' => $currentHeading, 'lines' => $current];
|
||||||
|
}
|
||||||
|
$currentHeading = $line;
|
||||||
|
$current = [$line];
|
||||||
|
} else {
|
||||||
|
$current[] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($currentHeading !== null) {
|
||||||
|
$sections[] = ['heading' => $currentHeading, 'lines' => $current];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the header (everything before the first ## section)
|
||||||
|
$header = [];
|
||||||
|
$contentLines = explode("\n", $content);
|
||||||
|
foreach ($contentLines as $line) {
|
||||||
|
if (preg_match('/^## /', $line)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$header[] = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate [Unreleased] from versioned sections
|
||||||
|
$unreleased = null;
|
||||||
|
$versioned = [];
|
||||||
|
|
||||||
|
foreach ($sections as $section) {
|
||||||
|
if (preg_match('/\[Unreleased\]/i', $section['heading'])) {
|
||||||
|
$unreleased = $section;
|
||||||
|
} else {
|
||||||
|
$versioned[] = $section;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalVersioned = count($versioned);
|
||||||
|
$pruned = $totalVersioned - $keep;
|
||||||
|
|
||||||
|
if ($pruned <= 0) {
|
||||||
|
echo "CHANGELOG has {$totalVersioned} versioned entries — nothing to prune (keeping {$keep})\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep only the first N versioned sections
|
||||||
|
$keptVersioned = array_slice($versioned, 0, $keep);
|
||||||
|
$droppedVersioned = array_slice($versioned, $keep);
|
||||||
|
|
||||||
|
// Report
|
||||||
|
echo "CHANGELOG: {$totalVersioned} versioned entries found\n";
|
||||||
|
echo " Keeping: {$keep} most recent\n";
|
||||||
|
echo " Pruning: {$pruned} old entries\n";
|
||||||
|
|
||||||
|
foreach ($droppedVersioned as $section) {
|
||||||
|
$heading = trim($section['heading']);
|
||||||
|
echo " - {$heading}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
echo "\n(dry-run) No changes written\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the file
|
||||||
|
$output = implode("\n", $header);
|
||||||
|
|
||||||
|
if ($unreleased !== null) {
|
||||||
|
$output .= implode("\n", $unreleased['lines']) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($keptVersioned as $section) {
|
||||||
|
$output .= implode("\n", $section['lines']) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up excessive blank lines at end
|
||||||
|
$output = rtrim($output) . "\n";
|
||||||
|
|
||||||
|
file_put_contents($changelog, $output);
|
||||||
|
echo "\nCHANGELOG pruned: removed {$pruned} old entries\n";
|
||||||
|
exit(0);
|
||||||
@@ -34,11 +34,11 @@ final class ClientDashboard
|
|||||||
$this->parseArgs();
|
$this->parseArgs();
|
||||||
|
|
||||||
if ($this->token === '') {
|
if ($this->token === '') {
|
||||||
$this->token = getenv('GA_TOKEN') ?: '';
|
$this->token = getenv('MOKOGITEA_TOKEN') ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->token === '') {
|
if ($this->token === '') {
|
||||||
$this->log('ERROR: --token or GA_TOKEN required.');
|
$this->log('ERROR: --token or MOKOGITEA_TOKEN required.');
|
||||||
$this->printUsage();
|
$this->printUsage();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -509,7 +509,7 @@ CARD;
|
|||||||
$this->log('Generate unified client status dashboard (HTML).');
|
$this->log('Generate unified client status dashboard (HTML).');
|
||||||
$this->log('');
|
$this->log('');
|
||||||
$this->log('Options:');
|
$this->log('Options:');
|
||||||
$this->log(' --token <token> Gitea token (or GA_TOKEN)');
|
$this->log(' --token <token> Gitea token (or MOKOGITEA_TOKEN)');
|
||||||
$this->log(' --gitea-url <url> Gitea URL');
|
$this->log(' --gitea-url <url> Gitea URL');
|
||||||
$this->log(' --org <org> Primary org (default: MokoConsulting)');
|
$this->log(' --org <org> Primary org (default: MokoConsulting)');
|
||||||
$this->log(' -o, --output <file> Output HTML file (default: stdout)');
|
$this->log(' -o, --output <file> Output HTML file (default: stdout)');
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ final class ClientProvision
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->giteaToken = $this->config['gitea_token']
|
$this->giteaToken = $this->config['gitea_token']
|
||||||
?? getenv('GA_TOKEN') ?: '';
|
?? getenv('MOKOGITEA_TOKEN') ?: '';
|
||||||
$this->grafanaUrl = $this->config['grafana_url']
|
$this->grafanaUrl = $this->config['grafana_url']
|
||||||
?? getenv('GRAFANA_URL') ?: '';
|
?? getenv('GRAFANA_URL') ?: '';
|
||||||
$this->grafanaToken = $this->config['grafana_token']
|
$this->grafanaToken = $this->config['grafana_token']
|
||||||
@@ -65,7 +65,7 @@ final class ClientProvision
|
|||||||
?? $this->giteaUrl;
|
?? $this->giteaUrl;
|
||||||
|
|
||||||
if ($this->giteaToken === '') {
|
if ($this->giteaToken === '') {
|
||||||
$this->log('ERROR: gitea_token or GA_TOKEN required.');
|
$this->log('ERROR: gitea_token or MOKOGITEA_TOKEN required.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +459,7 @@ final class ClientProvision
|
|||||||
$this->log(' --help, -h Show this help');
|
$this->log(' --help, -h Show this help');
|
||||||
$this->log('');
|
$this->log('');
|
||||||
$this->log('Environment variables:');
|
$this->log('Environment variables:');
|
||||||
$this->log(' GA_TOKEN Gitea API token');
|
$this->log(' MOKOGITEA_TOKEN Gitea API token');
|
||||||
$this->log(' GRAFANA_URL Grafana instance URL');
|
$this->log(' GRAFANA_URL Grafana instance URL');
|
||||||
$this->log(' GRAFANA_TOKEN Grafana API token');
|
$this->log(' GRAFANA_TOKEN Grafana API token');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ foreach ($argv as $i => $arg) {
|
|||||||
if ($arg === '--output-summary') $outputSummary = true;
|
if ($arg === '--output-summary') $outputSummary = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($token === null) $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
if ($token === null) $token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
||||||
|
|
||||||
if ($token === null || $apiBase === null) {
|
if ($token === null || $apiBase === null) {
|
||||||
fwrite(STDERR, "Usage: dev_branch_reset.php --token TOKEN --api-base URL [--branch dev] [--from main]\n");
|
fwrite(STDERR, "Usage: dev_branch_reset.php --token TOKEN --api-base URL [--branch dev] [--from main]\n");
|
||||||
|
|||||||
+14
-6
@@ -26,10 +26,18 @@ require_once __DIR__ . '/../vendor/autoload.php';
|
|||||||
|
|
||||||
use MokoEnterprise\{ApiClient, AuditLogger, CliFramework, Config, PlatformAdapterFactory};
|
use MokoEnterprise\{ApiClient, AuditLogger, CliFramework, Config, PlatformAdapterFactory};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joomla Release Manager
|
||||||
|
*
|
||||||
|
* Creates and manages Joomla extension releases on Gitea, including
|
||||||
|
* package building, asset upload, and update stream management.
|
||||||
|
*
|
||||||
|
* @since 04.06.00
|
||||||
|
*/
|
||||||
class JoomlaRelease extends CliFramework
|
class JoomlaRelease extends CliFramework
|
||||||
{
|
{
|
||||||
private const VERSION = '04.06.00';
|
private const VERSION = '04.06.00';
|
||||||
private const ORG = 'mokoconsulting-tech';
|
private const ORG = 'MokoConsulting';
|
||||||
|
|
||||||
private const STABILITY_TAGS = [
|
private const STABILITY_TAGS = [
|
||||||
'development' => 'development',
|
'development' => 'development',
|
||||||
@@ -48,7 +56,6 @@ class JoomlaRelease extends CliFramework
|
|||||||
];
|
];
|
||||||
|
|
||||||
private ApiClient $api;
|
private ApiClient $api;
|
||||||
private AuditLogger $logger;
|
|
||||||
private \MokoEnterprise\GitPlatformAdapter $adapter;
|
private \MokoEnterprise\GitPlatformAdapter $adapter;
|
||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
@@ -68,7 +75,7 @@ class JoomlaRelease extends CliFramework
|
|||||||
$stability = (string) $this->getArgument('--stability');
|
$stability = (string) $this->getArgument('--stability');
|
||||||
$dryRun = (bool) $this->getArgument('--dry-run');
|
$dryRun = (bool) $this->getArgument('--dry-run');
|
||||||
|
|
||||||
if (!isset(self::STABILITY_TAGS[$stability])) {
|
if (!array_key_exists($stability, self::STABILITY_TAGS)) {
|
||||||
$this->log('ERROR', "Invalid stability: {$stability}. Use: " . implode(', ', array_keys(self::STABILITY_TAGS)));
|
$this->log('ERROR', "Invalid stability: {$stability}. Use: " . implode(', ', array_keys(self::STABILITY_TAGS)));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -76,7 +83,6 @@ class JoomlaRelease extends CliFramework
|
|||||||
$config = Config::load();
|
$config = Config::load();
|
||||||
$this->adapter = PlatformAdapterFactory::create($config);
|
$this->adapter = PlatformAdapterFactory::create($config);
|
||||||
$this->api = $this->adapter->getApiClient();
|
$this->api = $this->adapter->getApiClient();
|
||||||
$this->logger = new AuditLogger('joomla_release');
|
|
||||||
|
|
||||||
if ($repo !== '') {
|
if ($repo !== '') {
|
||||||
$path = $this->cloneRepo($repo);
|
$path = $this->cloneRepo($repo);
|
||||||
@@ -207,7 +213,9 @@ class JoomlaRelease extends CliFramework
|
|||||||
|
|
||||||
// Templates don't have <element> — derive from <name>
|
// Templates don't have <element> — derive from <name>
|
||||||
if ($element === '') {
|
if ($element === '') {
|
||||||
$element = strtolower(str_replace(' ', '', $name));
|
// Strip type prefix (e.g. "Template - ") before deriving element
|
||||||
|
$baseName = preg_replace('/^(Package|Plugin|Module|Component|Template|Library|File)\s*-\s*/i', '', $name);
|
||||||
|
$element = strtolower(str_replace([' ', '-'], '', $baseName));
|
||||||
}
|
}
|
||||||
|
|
||||||
$tp = '';
|
$tp = '';
|
||||||
@@ -429,7 +437,7 @@ class JoomlaRelease extends CliFramework
|
|||||||
$lines[] = ' <tags>';
|
$lines[] = ' <tags>';
|
||||||
$lines[] = " <tag>{$stability}</tag>";
|
$lines[] = " <tag>{$stability}</tag>";
|
||||||
$lines[] = ' </tags>';
|
$lines[] = ' </tags>';
|
||||||
$lines[] = " <infourl title=\"{$meta['name']}\">https://github.com/" . self::ORG . "</infourl>";
|
$lines[] = " <infourl title=\"{$meta['name']}\">https://git.mokoconsulting.tech/" . self::ORG . "</infourl>";
|
||||||
$lines[] = ' <downloads>';
|
$lines[] = ' <downloads>';
|
||||||
$lines[] = " <downloadurl type=\"full\" format=\"zip\">{$zipUrl}</downloadurl>";
|
$lines[] = " <downloadurl type=\"full\" format=\"zip\">{$zipUrl}</downloadurl>";
|
||||||
$lines[] = " <downloadurl type=\"full\" format=\"tar.gz\">{$tarUrl}</downloadurl>";
|
$lines[] = " <downloadurl type=\"full\" format=\"tar.gz\">{$tarUrl}</downloadurl>";
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/manifest_element.php
|
||||||
|
* BRIEF: Extract element name, type, type prefix, and ZIP name from manifest
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php manifest_element.php --path .
|
||||||
|
* php manifest_element.php --path . --version 09.01.00 --stability dev --github-output
|
||||||
|
*
|
||||||
|
* Detects platform (joomla, dolibarr, generic) and resolves:
|
||||||
|
* ext_element — canonical element name (e.g. mokojgdpc)
|
||||||
|
* ext_type — extension type (plugin, module, component, package, etc.)
|
||||||
|
* ext_folder — group/folder for plugins (e.g. system)
|
||||||
|
* ext_name — human-readable name (e.g. "Moko JGDPC")
|
||||||
|
* type_prefix — Joomla type prefix (plg_system_, com_, mod_, etc.)
|
||||||
|
* zip_name — computed ZIP filename
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$path = '.';
|
||||||
|
$version = null;
|
||||||
|
$stability = 'stable';
|
||||||
|
$githubOutput = false;
|
||||||
|
$repoName = '';
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--path' && isset($argv[$i + 1])) {
|
||||||
|
$path = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--version' && isset($argv[$i + 1])) {
|
||||||
|
$version = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--stability' && isset($argv[$i + 1])) {
|
||||||
|
$stability = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--repo' && isset($argv[$i + 1])) {
|
||||||
|
$repoName = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--github-output') {
|
||||||
|
$githubOutput = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
|
// ── Detect platform from manifest.xml ────────────────────────────────────────
|
||||||
|
$platform = 'generic';
|
||||||
|
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
if (file_exists($manifestXml)) {
|
||||||
|
$content = file_get_contents($manifestXml);
|
||||||
|
if (preg_match('/<platform>([^<]+)<\/platform>/', $content, $pm)) {
|
||||||
|
$platform = trim($pm[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Find extension manifest (Joomla XML) ─────────────────────────────────────
|
||||||
|
$extManifest = null;
|
||||||
|
$manifestFiles = array_merge(
|
||||||
|
glob("{$root}/src/pkg_*.xml") ?: [],
|
||||||
|
glob("{$root}/src/*.xml") ?: [],
|
||||||
|
glob("{$root}/*.xml") ?: []
|
||||||
|
);
|
||||||
|
foreach ($manifestFiles as $file) {
|
||||||
|
$c = file_get_contents($file);
|
||||||
|
if (strpos($c, '<extension') !== false) {
|
||||||
|
$extManifest = $file;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Find Dolibarr module file ────────────────────────────────────────────────
|
||||||
|
$modFile = null;
|
||||||
|
$modFiles = array_merge(
|
||||||
|
glob("{$root}/src/core/modules/mod*.class.php") ?: [],
|
||||||
|
glob("{$root}/htdocs/core/modules/mod*.class.php") ?: [],
|
||||||
|
glob("{$root}/core/modules/mod*.class.php") ?: []
|
||||||
|
);
|
||||||
|
foreach ($modFiles as $file) {
|
||||||
|
$c = file_get_contents($file);
|
||||||
|
if (strpos($c, 'extends DolibarrModules') !== false) {
|
||||||
|
$modFile = $file;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Extract metadata ─────────────────────────────────────────────────────────
|
||||||
|
$extElement = '';
|
||||||
|
$extType = '';
|
||||||
|
$extFolder = '';
|
||||||
|
$extName = '';
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
// Joomla platforms
|
||||||
|
case in_array($platform, ['joomla', 'waas-component'], true) && $extManifest !== null:
|
||||||
|
$xml = file_get_contents($extManifest);
|
||||||
|
|
||||||
|
// Extension type and folder
|
||||||
|
if (preg_match('/type="([^"]*)"/', $xml, $tm)) {
|
||||||
|
$extType = $tm[1];
|
||||||
|
}
|
||||||
|
if (preg_match('/group="([^"]*)"/', $xml, $gm)) {
|
||||||
|
$extFolder = $gm[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element name: <element>, module= attribute, plugin= attribute, <packagename>, or filename
|
||||||
|
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $em)) {
|
||||||
|
$extElement = $em[1];
|
||||||
|
}
|
||||||
|
if (empty($extElement) && preg_match('/module="([^"]*)"/', $xml, $mm)) {
|
||||||
|
$extElement = $mm[1];
|
||||||
|
}
|
||||||
|
if (empty($extElement) && preg_match('/plugin="([^"]*)"/', $xml, $pm)) {
|
||||||
|
$extElement = $pm[1];
|
||||||
|
}
|
||||||
|
if ($extType === 'package' && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $pn)) {
|
||||||
|
$extElement = $pn[1];
|
||||||
|
}
|
||||||
|
if (empty($extElement)) {
|
||||||
|
$extElement = strtolower(basename($extManifest, '.xml'));
|
||||||
|
if (in_array($extElement, ['templatedetails', 'manifest'], true)) {
|
||||||
|
$extElement = strtolower(str_replace([' ', '-'], '', $repoName ?: basename($root)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human-readable name
|
||||||
|
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $nm)) {
|
||||||
|
$extName = trim($nm[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Dolibarr platforms
|
||||||
|
case in_array($platform, ['dolibarr', 'crm-module'], true) && $modFile !== null:
|
||||||
|
$extType = 'dolibarr-module';
|
||||||
|
$modBasename = basename($modFile, '.class.php');
|
||||||
|
$extElement = strtolower(preg_replace('/^mod/', '', $modBasename));
|
||||||
|
|
||||||
|
$modContent = file_get_contents($modFile);
|
||||||
|
if (preg_match('/\$this->name\s*=\s*[\'"]([^\'"]+)[\'"]/', $modContent, $nm)) {
|
||||||
|
$extName = $nm[1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Generic / fallback
|
||||||
|
default:
|
||||||
|
$extElement = strtolower(str_replace([' ', '-'], '', $repoName ?: basename($root)));
|
||||||
|
$extType = 'generic';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Strip existing type prefix from element to prevent duplication ────────────
|
||||||
|
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $extElement);
|
||||||
|
|
||||||
|
// ── Compute type prefix ──────────────────────────────────────────────────────
|
||||||
|
$typePrefix = '';
|
||||||
|
switch ($extType) {
|
||||||
|
case 'plugin':
|
||||||
|
$typePrefix = "plg_{$extFolder}_";
|
||||||
|
break;
|
||||||
|
case 'module':
|
||||||
|
$typePrefix = 'mod_';
|
||||||
|
break;
|
||||||
|
case 'component':
|
||||||
|
$typePrefix = 'com_';
|
||||||
|
break;
|
||||||
|
case 'template':
|
||||||
|
$typePrefix = 'tpl_';
|
||||||
|
break;
|
||||||
|
case 'library':
|
||||||
|
$typePrefix = 'lib_';
|
||||||
|
break;
|
||||||
|
case 'package':
|
||||||
|
$typePrefix = 'pkg_';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Compute ZIP name ─────────────────────────────────────────────────────────
|
||||||
|
$suffixMap = [
|
||||||
|
'development' => '-dev',
|
||||||
|
'dev' => '-dev',
|
||||||
|
'alpha' => '-alpha',
|
||||||
|
'beta' => '-beta',
|
||||||
|
'rc' => '-rc',
|
||||||
|
'release-candidate' => '-rc',
|
||||||
|
'stable' => '',
|
||||||
|
];
|
||||||
|
$suffix = $suffixMap[$stability] ?? '';
|
||||||
|
$zipName = '';
|
||||||
|
if ($version !== null) {
|
||||||
|
$zipName = "{$typePrefix}{$extElement}-{$version}{$suffix}.zip";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback name
|
||||||
|
if (empty($extName)) {
|
||||||
|
$extName = $repoName ?: basename($root);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Output ───────────────────────────────────────────────────────────────────
|
||||||
|
$outputs = [
|
||||||
|
'platform' => $platform,
|
||||||
|
'ext_element' => $extElement,
|
||||||
|
'ext_type' => $extType,
|
||||||
|
'ext_folder' => $extFolder,
|
||||||
|
'ext_name' => $extName,
|
||||||
|
'type_prefix' => $typePrefix,
|
||||||
|
'zip_name' => $zipName,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($githubOutput) {
|
||||||
|
$ghOutput = getenv('GITHUB_OUTPUT');
|
||||||
|
$lines = [];
|
||||||
|
foreach ($outputs as $key => $value) {
|
||||||
|
$lines[] = "{$key}={$value}";
|
||||||
|
}
|
||||||
|
if ($ghOutput) {
|
||||||
|
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
|
||||||
|
} else {
|
||||||
|
// Fallback: echo ::set-output (legacy)
|
||||||
|
foreach ($outputs as $key => $value) {
|
||||||
|
echo "::set-output name={$key}::{$value}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($outputs as $key => $value) {
|
||||||
|
echo "{$key}={$value}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
@@ -103,6 +103,7 @@ if ($xml === false) {
|
|||||||
'language' => (string)($xml->build->language ?? ''),
|
'language' => (string)($xml->build->language ?? ''),
|
||||||
'package-type' => (string)($xml->build->{"package-type"} ?? ''),
|
'package-type' => (string)($xml->build->{"package-type"} ?? ''),
|
||||||
'entry-point' => (string)($xml->build->{"entry-point"} ?? ''),
|
'entry-point' => (string)($xml->build->{"entry-point"} ?? ''),
|
||||||
|
'version' => (string)($xml->identity->version ?? ''),
|
||||||
'source-dir' => (string)($xml->deploy->{"source-dir"} ?? ''),
|
'source-dir' => (string)($xml->deploy->{"source-dir"} ?? ''),
|
||||||
'remote-subdir' => (string)($xml->deploy->{"remote-subdir"} ?? ''),
|
'remote-subdir' => (string)($xml->deploy->{"remote-subdir"} ?? ''),
|
||||||
'excludes' => (string)($xml->deploy->excludes ?? ''),
|
'excludes' => (string)($xml->deploy->excludes ?? ''),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ foreach ($argv as $i => $arg) {
|
|||||||
if ($arg === '--output-summary') $outputSummary = true;
|
if ($arg === '--output-summary') $outputSummary = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($token === null) $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
if ($token === null) $token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
||||||
|
|
||||||
if ($version === null || $releaseTag === null || $token === null || $apiBase === null) {
|
if ($version === null || $releaseTag === null || $token === null || $apiBase === null) {
|
||||||
fwrite(STDERR, "Usage: release_body_update.php --version VER --release-tag TAG --token TOKEN --api-base URL\n");
|
fwrite(STDERR, "Usage: release_body_update.php --version VER --release-tag TAG --token TOKEN --api-base URL\n");
|
||||||
|
|||||||
+96
-5
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
@@ -14,12 +15,16 @@
|
|||||||
* Usage:
|
* Usage:
|
||||||
* php release_cascade.php --stability stable --token TOKEN --api-base URL
|
* php release_cascade.php --stability stable --token TOKEN --api-base URL
|
||||||
* php release_cascade.php --stability rc --token TOKEN --api-base URL
|
* php release_cascade.php --stability rc --token TOKEN --api-base URL
|
||||||
|
* php release_cascade.php --stability stable --version 09.01.00 --token TOKEN --api-base URL
|
||||||
*
|
*
|
||||||
* Cascade rules:
|
* Cascade rules:
|
||||||
* stable -> deletes development, alpha, beta, release-candidate
|
* stable -> deletes development, alpha, beta, release-candidate
|
||||||
* rc -> deletes development, alpha, beta
|
* rc -> deletes development, alpha, beta
|
||||||
* beta -> deletes development, alpha
|
* beta -> deletes development, alpha
|
||||||
* alpha -> deletes development
|
* alpha -> deletes development
|
||||||
|
*
|
||||||
|
* When --version is given, also deletes releases on any channel whose version
|
||||||
|
* is lower than the specified version (prevents stale pre-releases lingering).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -27,22 +32,32 @@ declare(strict_types=1);
|
|||||||
$stability = null;
|
$stability = null;
|
||||||
$token = null;
|
$token = null;
|
||||||
$apiBase = null;
|
$apiBase = null;
|
||||||
|
$version = null;
|
||||||
|
|
||||||
foreach ($argv as $i => $arg) {
|
foreach ($argv as $i => $arg) {
|
||||||
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
|
if ($arg === '--stability' && isset($argv[$i + 1])) {
|
||||||
if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1];
|
$stability = $argv[$i + 1];
|
||||||
if ($arg === '--api-base' && isset($argv[$i + 1])) $apiBase = $argv[$i + 1];
|
}
|
||||||
|
if ($arg === '--token' && isset($argv[$i + 1])) {
|
||||||
|
$token = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--api-base' && isset($argv[$i + 1])) {
|
||||||
|
$apiBase = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--version' && isset($argv[$i + 1])) {
|
||||||
|
$version = $argv[$i + 1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow token from environment
|
// Allow token from environment
|
||||||
if ($token === null) {
|
if ($token === null) {
|
||||||
$token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
$token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($stability === null || $token === null || $apiBase === null) {
|
if ($stability === null || $token === null || $apiBase === null) {
|
||||||
fwrite(STDERR, "Usage: release_cascade.php --stability [stable|rc|beta|alpha] --token TOKEN --api-base URL\n");
|
fwrite(STDERR, "Usage: release_cascade.php --stability [stable|rc|beta|alpha] --token TOKEN --api-base URL\n");
|
||||||
fwrite(STDERR, " --api-base: e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo\n");
|
fwrite(STDERR, " --api-base: e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo\n");
|
||||||
fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n");
|
fwrite(STDERR, " Token can also be set via MOKOGITEA_TOKEN or GITEA_TOKEN env var\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,5 +128,81 @@ foreach ($tagsToDelete as $tag) {
|
|||||||
$deleted++;
|
$deleted++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Version-aware cleanup: delete releases with lesser version numbers ───────
|
||||||
|
if ($version !== null) {
|
||||||
|
// Normalize version for comparison (strip any suffix)
|
||||||
|
$baseVersion = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $version);
|
||||||
|
|
||||||
|
// Check all channels (including ones not in the cascade map for this stability)
|
||||||
|
$allChannels = ['development', 'alpha', 'beta', 'release-candidate', 'stable'];
|
||||||
|
foreach ($allChannels as $tag) {
|
||||||
|
// Skip the current stability channel
|
||||||
|
if ($tag === $stability) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Skip channels already deleted by cascade above
|
||||||
|
if (in_array($tag, $tagsToDelete, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init("{$apiBase}/releases/tags/{$tag}");
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode !== 200 || empty($response)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
$releaseId = $data['id'] ?? null;
|
||||||
|
$releaseName = $data['name'] ?? '';
|
||||||
|
if ($releaseId === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract version from release name (e.g. "element 09.00.01 (development)")
|
||||||
|
$releaseVersion = null;
|
||||||
|
if (preg_match('/(\d{2}\.\d{2}\.\d{2})/', $releaseName, $vm)) {
|
||||||
|
$releaseVersion = $vm[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($releaseVersion === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete if release version is less than the promoted version
|
||||||
|
if (version_compare($releaseVersion, $baseVersion, '<')) {
|
||||||
|
$delCh = curl_init("{$apiBase}/releases/{$releaseId}");
|
||||||
|
curl_setopt_array($delCh, [
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
curl_exec($delCh);
|
||||||
|
curl_close($delCh);
|
||||||
|
|
||||||
|
$tagCh = curl_init("{$apiBase}/tags/{$tag}");
|
||||||
|
curl_setopt_array($tagCh, [
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
curl_exec($tagCh);
|
||||||
|
curl_close($tagCh);
|
||||||
|
|
||||||
|
echo "Deleted: {$tag} — version {$releaseVersion} < {$baseVersion}\n";
|
||||||
|
$deleted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
echo "Cleaned up {$deleted} pre-release channel(s)\n";
|
echo "Cleaned up {$deleted} pre-release channel(s)\n";
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|||||||
@@ -0,0 +1,328 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/release_create.php
|
||||||
|
* BRIEF: Create or overwrite a Gitea release with proper naming
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php release_create.php --version 09.01.00 --tag stable --token TOKEN --api-base URL
|
||||||
|
* php release_create.php --version 09.01.00 --tag development --token TOKEN --api-base URL --prerelease
|
||||||
|
* php release_create.php --version 09.01.00 --tag stable --token TOKEN --api-base URL --path . --repo MyRepo
|
||||||
|
*
|
||||||
|
* Replaces the inline bash in auto-release.yml Step 7b.
|
||||||
|
* Detects extension metadata from manifest, builds a proper release name,
|
||||||
|
* generates release notes, and creates (or overwrites) a Gitea release.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// ── Argument parsing ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$path = '.';
|
||||||
|
$version = null;
|
||||||
|
$tag = null;
|
||||||
|
$token = null;
|
||||||
|
$apiBase = null;
|
||||||
|
$branch = 'main';
|
||||||
|
$repoName = '';
|
||||||
|
$prerelease = false;
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--path' && isset($argv[$i + 1])) {
|
||||||
|
$path = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--version' && isset($argv[$i + 1])) {
|
||||||
|
$version = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--tag' && isset($argv[$i + 1])) {
|
||||||
|
$tag = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--token' && isset($argv[$i + 1])) {
|
||||||
|
$token = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--api-base' && isset($argv[$i + 1])) {
|
||||||
|
$apiBase = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--branch' && isset($argv[$i + 1])) {
|
||||||
|
$branch = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--repo' && isset($argv[$i + 1])) {
|
||||||
|
$repoName = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--prerelease') {
|
||||||
|
$prerelease = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow token from environment
|
||||||
|
if ($token === null) {
|
||||||
|
$envToken = getenv('MOKOGITEA_TOKEN');
|
||||||
|
if ($envToken === false || $envToken === '') {
|
||||||
|
$envToken = getenv('GITEA_TOKEN');
|
||||||
|
}
|
||||||
|
if ($envToken !== false && $envToken !== '') {
|
||||||
|
$token = $envToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($version === null || $tag === null || $token === null || $apiBase === null) {
|
||||||
|
fwrite(STDERR, "Usage: release_create.php --version VER --tag TAG --token TOKEN --api-base URL [options]\n");
|
||||||
|
fwrite(STDERR, " --path . Repo root for manifest detection (default: .)\n");
|
||||||
|
fwrite(STDERR, " --branch main Target commitish (default: main)\n");
|
||||||
|
fwrite(STDERR, " --repo REPO Repo name for fallback element detection\n");
|
||||||
|
fwrite(STDERR, " --prerelease Mark release as prerelease\n");
|
||||||
|
fwrite(STDERR, " Token can also be set via MOKOGITEA_TOKEN or GITEA_TOKEN env var\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helper: Gitea API request ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to the Gitea API.
|
||||||
|
*
|
||||||
|
* @param string $url Full API URL
|
||||||
|
* @param string $token Authorization token
|
||||||
|
* @param string $method HTTP method (GET, POST, DELETE, etc.)
|
||||||
|
* @param string|null $body JSON request body
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>|null Decoded response or null on failure
|
||||||
|
*/
|
||||||
|
function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
if ($ch === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Content-Type: application/json',
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
]);
|
||||||
|
if ($body !== null) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
|
}
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300 || empty($response) || !is_string($response)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = json_decode($response, true);
|
||||||
|
return is_array($decoded) ? $decoded : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Detect element metadata ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
|
$extElement = '';
|
||||||
|
$extType = '';
|
||||||
|
$extFolder = '';
|
||||||
|
$extName = '';
|
||||||
|
$typePrefix = '';
|
||||||
|
|
||||||
|
// Detect platform from manifest.xml
|
||||||
|
$platform = 'generic';
|
||||||
|
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
if (file_exists($manifestXml)) {
|
||||||
|
$content = file_get_contents($manifestXml);
|
||||||
|
if ($content !== false && preg_match('/<platform>([^<]+)<\/platform>/', $content, $pm)) {
|
||||||
|
$platform = trim($pm[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find extension manifest (Joomla XML)
|
||||||
|
$extManifest = null;
|
||||||
|
$manifestFiles = array_merge(
|
||||||
|
glob("{$root}/src/pkg_*.xml") ?: [],
|
||||||
|
glob("{$root}/src/*.xml") ?: [],
|
||||||
|
glob("{$root}/*.xml") ?: []
|
||||||
|
);
|
||||||
|
foreach ($manifestFiles as $file) {
|
||||||
|
$c = file_get_contents($file);
|
||||||
|
if ($c !== false && strpos($c, '<extension') !== false) {
|
||||||
|
$extManifest = $file;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find Dolibarr module file
|
||||||
|
$modFile = null;
|
||||||
|
$modFiles = array_merge(
|
||||||
|
glob("{$root}/src/core/modules/mod*.class.php") ?: [],
|
||||||
|
glob("{$root}/htdocs/core/modules/mod*.class.php") ?: [],
|
||||||
|
glob("{$root}/core/modules/mod*.class.php") ?: []
|
||||||
|
);
|
||||||
|
foreach ($modFiles as $file) {
|
||||||
|
$c = file_get_contents($file);
|
||||||
|
if ($c !== false && strpos($c, 'extends DolibarrModules') !== false) {
|
||||||
|
$modFile = $file;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract metadata based on platform
|
||||||
|
switch (true) {
|
||||||
|
case in_array($platform, ['joomla', 'waas-component'], true) && $extManifest !== null:
|
||||||
|
$xml = file_get_contents($extManifest);
|
||||||
|
if ($xml === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/type="([^"]*)"/', $xml, $tm)) {
|
||||||
|
$extType = $tm[1];
|
||||||
|
}
|
||||||
|
if (preg_match('/group="([^"]*)"/', $xml, $gm)) {
|
||||||
|
$extFolder = $gm[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element name: <element>, plugin= attribute, <packagename>, or filename
|
||||||
|
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $em)) {
|
||||||
|
$extElement = $em[1];
|
||||||
|
}
|
||||||
|
if (empty($extElement) && preg_match('/plugin="([^"]*)"/', $xml, $pm2)) {
|
||||||
|
$extElement = $pm2[1];
|
||||||
|
}
|
||||||
|
if ($extType === 'package' && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $pn)) {
|
||||||
|
$extElement = $pn[1];
|
||||||
|
}
|
||||||
|
if (empty($extElement)) {
|
||||||
|
$extElement = strtolower(basename($extManifest, '.xml'));
|
||||||
|
if (in_array($extElement, ['templatedetails', 'manifest'], true)) {
|
||||||
|
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human-readable name
|
||||||
|
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $nm)) {
|
||||||
|
$extName = trim($nm[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case in_array($platform, ['dolibarr', 'crm-module'], true) && $modFile !== null:
|
||||||
|
$extType = 'dolibarr-module';
|
||||||
|
$modBasename = basename($modFile, '.class.php');
|
||||||
|
$extElement = strtolower(preg_replace('/^mod/', '', $modBasename) ?? $modBasename);
|
||||||
|
|
||||||
|
$modContent = file_get_contents($modFile);
|
||||||
|
if ($modContent !== false && preg_match('/\$this->name\s*=\s*[\'"]([^\'"]+)[\'"]/', $modContent, $nm2)) {
|
||||||
|
$extName = $nm2[1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
|
||||||
|
$extType = 'generic';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip existing type prefix from element to prevent duplication
|
||||||
|
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $extElement) ?? $extElement;
|
||||||
|
|
||||||
|
// Compute type prefix
|
||||||
|
switch ($extType) {
|
||||||
|
case 'plugin':
|
||||||
|
$typePrefix = "plg_{$extFolder}_";
|
||||||
|
break;
|
||||||
|
case 'module':
|
||||||
|
$typePrefix = 'mod_';
|
||||||
|
break;
|
||||||
|
case 'component':
|
||||||
|
$typePrefix = 'com_';
|
||||||
|
break;
|
||||||
|
case 'template':
|
||||||
|
$typePrefix = 'tpl_';
|
||||||
|
break;
|
||||||
|
case 'library':
|
||||||
|
$typePrefix = 'lib_';
|
||||||
|
break;
|
||||||
|
case 'package':
|
||||||
|
$typePrefix = 'pkg_';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback name
|
||||||
|
if (empty($extName)) {
|
||||||
|
$extName = $repoName !== '' ? $repoName : basename($root);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Element: {$extElement}, Type: {$extType}, Prefix: {$typePrefix}, Name: {$extName}\n";
|
||||||
|
|
||||||
|
// ── Build release name ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$releaseName = "{$extName} {$version} ({$typePrefix}{$extElement}-{$version})";
|
||||||
|
echo "Release name: {$releaseName}\n";
|
||||||
|
|
||||||
|
// ── Generate release notes ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$releaseNotes = "Release {$version}";
|
||||||
|
$releaseNotesScript = dirname(__DIR__) . '/cli/release_notes.php';
|
||||||
|
if (file_exists($releaseNotesScript)) {
|
||||||
|
$cmd = sprintf(
|
||||||
|
'php %s --path %s --version %s',
|
||||||
|
escapeshellarg($releaseNotesScript),
|
||||||
|
escapeshellarg($root),
|
||||||
|
escapeshellarg($version)
|
||||||
|
);
|
||||||
|
$output = [];
|
||||||
|
$exitCode = 0;
|
||||||
|
exec($cmd, $output, $exitCode);
|
||||||
|
if ($exitCode === 0 && count($output) > 0) {
|
||||||
|
$notes = implode("\n", $output);
|
||||||
|
if (trim($notes) !== '') {
|
||||||
|
$releaseNotes = $notes;
|
||||||
|
echo "Release notes: generated from CHANGELOG.md\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Delete existing release at tag (if present) ─────────────────────────────
|
||||||
|
|
||||||
|
$existing = giteaApi("{$apiBase}/releases/tags/{$tag}", $token);
|
||||||
|
if ($existing !== null && !empty($existing['id'])) {
|
||||||
|
$existingId = $existing['id'];
|
||||||
|
echo "Deleting existing release: {$tag} (id: {$existingId})\n";
|
||||||
|
|
||||||
|
// Delete release
|
||||||
|
giteaApi("{$apiBase}/releases/{$existingId}", $token, 'DELETE');
|
||||||
|
|
||||||
|
// Delete tag
|
||||||
|
giteaApi("{$apiBase}/tags/{$tag}", $token, 'DELETE');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Create new release ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$payload = json_encode([
|
||||||
|
'tag_name' => $tag,
|
||||||
|
'target_commitish' => $branch,
|
||||||
|
'name' => $releaseName,
|
||||||
|
'body' => $releaseNotes,
|
||||||
|
'prerelease' => $prerelease,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newRelease = giteaApi("{$apiBase}/releases", $token, 'POST', $payload !== false ? $payload : '{}');
|
||||||
|
if ($newRelease === null || empty($newRelease['id'])) {
|
||||||
|
fwrite(STDERR, "Failed to create release at tag: {$tag}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$releaseId = $newRelease['id'];
|
||||||
|
echo "Created release: {$tag} (id: {$releaseId})\n";
|
||||||
|
|
||||||
|
// Output release_id to stdout for CI consumption
|
||||||
|
echo "release_id={$releaseId}\n";
|
||||||
|
exit(0);
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
* --body-file Read body from file instead of --body
|
* --body-file Read body from file instead of --body
|
||||||
* --target Target branch/commitish (for create, default: main)
|
* --target Target branch/commitish (for create, default: main)
|
||||||
* --files Comma-separated file paths to upload (for upload)
|
* --files Comma-separated file paths to upload (for upload)
|
||||||
* --token Gitea API token (or GA_TOKEN/GITEA_TOKEN env var)
|
* --token Gitea API token (or MOKOGITEA_TOKEN/GITEA_TOKEN env var)
|
||||||
* --api-base Gitea API base URL (e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo)
|
* --api-base Gitea API base URL (e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo)
|
||||||
*
|
*
|
||||||
* NOTE: This script uses PHP curl for all HTTP operations (no shell calls).
|
* NOTE: This script uses PHP curl for all HTTP operations (no shell calls).
|
||||||
@@ -67,7 +67,7 @@ foreach ($argv as $i => $arg) {
|
|||||||
|
|
||||||
// Allow token from environment
|
// Allow token from environment
|
||||||
if ($token === null) {
|
if ($token === null) {
|
||||||
$token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
$token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read body from file if specified
|
// Read body from file if specified
|
||||||
|
|||||||
@@ -0,0 +1,300 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/release_mirror.php
|
||||||
|
* BRIEF: Mirror a Gitea release (with assets) to a GitHub repository
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php release_mirror.php --version 09.01.00 --tag stable --token TOKEN --api-base URL \
|
||||||
|
* --gh-token GH_MIRROR_TOKEN --gh-repo MokoConsulting/MokoWaaS
|
||||||
|
*
|
||||||
|
* Mirrors a Gitea release (title, body, assets) to a corresponding GitHub release.
|
||||||
|
* If the GitHub release already exists at the same tag, its title is updated via PATCH.
|
||||||
|
* All assets from the Gitea release are downloaded and uploaded to the GitHub release.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// ── Argument parsing ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$version = null;
|
||||||
|
$tag = null;
|
||||||
|
$token = null;
|
||||||
|
$apiBase = null;
|
||||||
|
$ghToken = null;
|
||||||
|
$ghRepo = null;
|
||||||
|
$branch = 'main';
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--version' && isset($argv[$i + 1])) {
|
||||||
|
$version = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--tag' && isset($argv[$i + 1])) {
|
||||||
|
$tag = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--token' && isset($argv[$i + 1])) {
|
||||||
|
$token = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--api-base' && isset($argv[$i + 1])) {
|
||||||
|
$apiBase = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--gh-token' && isset($argv[$i + 1])) {
|
||||||
|
$ghToken = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--gh-repo' && isset($argv[$i + 1])) {
|
||||||
|
$ghRepo = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--branch' && isset($argv[$i + 1])) {
|
||||||
|
$branch = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow tokens from environment
|
||||||
|
$token = $token ?: (getenv('MOKOGITEA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: null));
|
||||||
|
$ghToken = $ghToken ?: (getenv('GH_MIRROR_TOKEN') ?: null);
|
||||||
|
|
||||||
|
if (
|
||||||
|
$version === null || $tag === null || $token === null || $apiBase === null
|
||||||
|
|| $ghToken === null || $ghRepo === null
|
||||||
|
) {
|
||||||
|
fwrite(STDERR, "Usage: release_mirror.php --version VER --tag TAG --token TOKEN " .
|
||||||
|
"--api-base URL --gh-token GH_MIRROR_TOKEN --gh-repo org/repo [--branch main]\n");
|
||||||
|
fwrite(STDERR, " --token: Gitea token (or MOKOGITEA_TOKEN / GITEA_TOKEN env)\n");
|
||||||
|
fwrite(STDERR, " --gh-token: GitHub token (or GH_MIRROR_TOKEN env)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helper: Gitea API request ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to the Gitea API.
|
||||||
|
*
|
||||||
|
* @param string $url Full Gitea API URL
|
||||||
|
* @param string $token Gitea API token
|
||||||
|
* @param string $method HTTP method (GET, POST, PATCH, DELETE)
|
||||||
|
* @param string|null $body JSON request body or null
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>|null Decoded response or null on failure
|
||||||
|
*/
|
||||||
|
function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Content-Type: application/json',
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
]);
|
||||||
|
if ($body !== null) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
|
}
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300 || empty($response)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return json_decode($response, true) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file from Gitea to a local path.
|
||||||
|
*
|
||||||
|
* @param string $url Download URL
|
||||||
|
* @param string $token Gitea API token
|
||||||
|
* @param string $dest Local destination path
|
||||||
|
*
|
||||||
|
* @return bool True on success
|
||||||
|
*/
|
||||||
|
function giteaDownload(string $url, string $token, string $dest): bool
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
$fp = fopen($dest, 'wb');
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||||
|
CURLOPT_FILE => $fp,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_TIMEOUT => 120,
|
||||||
|
]);
|
||||||
|
curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
fclose($fp);
|
||||||
|
return $httpCode >= 200 && $httpCode < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to the GitHub API.
|
||||||
|
*
|
||||||
|
* @param string $url Full GitHub API URL
|
||||||
|
* @param string $token GitHub personal access token
|
||||||
|
* @param string $method HTTP method (GET, POST, PATCH, DELETE)
|
||||||
|
* @param string|null $body JSON request body or null
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>|null Decoded response or null on failure
|
||||||
|
*/
|
||||||
|
function githubApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Accept: application/vnd.github+json',
|
||||||
|
'User-Agent: moko-platform',
|
||||||
|
'Content-Type: application/json',
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
]);
|
||||||
|
if ($body !== null) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
|
}
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300 || empty($response)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return json_decode($response, true) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a binary asset to a GitHub release.
|
||||||
|
*
|
||||||
|
* @param string $uploadUrl GitHub upload URL (uploads.github.com)
|
||||||
|
* @param string $token GitHub personal access token
|
||||||
|
* @param string $filePath Local file path to upload
|
||||||
|
* @param string $name Asset filename for GitHub
|
||||||
|
*
|
||||||
|
* @return int HTTP status code
|
||||||
|
*/
|
||||||
|
function githubUploadAsset(string $uploadUrl, string $token, string $filePath, string $name): int
|
||||||
|
{
|
||||||
|
$url = $uploadUrl . '?name=' . urlencode($name);
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Accept: application/vnd.github+json',
|
||||||
|
'User-Agent: moko-platform',
|
||||||
|
'Content-Type: application/octet-stream',
|
||||||
|
],
|
||||||
|
CURLOPT_POSTFIELDS => file_get_contents($filePath),
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 120,
|
||||||
|
]);
|
||||||
|
curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
return $httpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Step 1: Get Gitea release by tag ─────────────────────────────────────────
|
||||||
|
|
||||||
|
echo "Fetching Gitea release: {$tag}\n";
|
||||||
|
$giteaRelease = giteaApi("{$apiBase}/releases/tags/{$tag}", $token);
|
||||||
|
if (!$giteaRelease || empty($giteaRelease['id'])) {
|
||||||
|
fwrite(STDERR, "No Gitea release found with tag: {$tag}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$giteaId = $giteaRelease['id'];
|
||||||
|
$releaseName = $giteaRelease['name'] ?? "{$version}";
|
||||||
|
$releaseBody = $giteaRelease['body'] ?? '';
|
||||||
|
$assets = $giteaRelease['assets'] ?? [];
|
||||||
|
|
||||||
|
echo " Name: {$releaseName}\n";
|
||||||
|
echo " Assets: " . count($assets) . " file(s)\n";
|
||||||
|
|
||||||
|
// ── Step 2: Check / create GitHub release ────────────────────────────────────
|
||||||
|
|
||||||
|
$ghApiBase = "https://api.github.com/repos/{$ghRepo}";
|
||||||
|
$ghUploadBase = "https://uploads.github.com/repos/{$ghRepo}";
|
||||||
|
|
||||||
|
echo "Checking GitHub release: {$tag}\n";
|
||||||
|
$ghRelease = githubApi("{$ghApiBase}/releases/tags/{$tag}", $ghToken);
|
||||||
|
|
||||||
|
if ($ghRelease && !empty($ghRelease['id'])) {
|
||||||
|
// Update existing release title
|
||||||
|
$ghReleaseId = $ghRelease['id'];
|
||||||
|
echo " GitHub release exists (id: {$ghReleaseId}), updating title\n";
|
||||||
|
$patchPayload = json_encode([
|
||||||
|
'name' => $releaseName,
|
||||||
|
'body' => $releaseBody,
|
||||||
|
]);
|
||||||
|
githubApi("{$ghApiBase}/releases/{$ghReleaseId}", $ghToken, 'PATCH', $patchPayload);
|
||||||
|
} else {
|
||||||
|
// Create new release
|
||||||
|
echo " Creating GitHub release\n";
|
||||||
|
$createPayload = json_encode([
|
||||||
|
'tag_name' => $tag,
|
||||||
|
'target_commitish' => $branch,
|
||||||
|
'name' => $releaseName,
|
||||||
|
'body' => $releaseBody,
|
||||||
|
'draft' => false,
|
||||||
|
'prerelease' => ($tag !== 'stable'),
|
||||||
|
]);
|
||||||
|
$ghRelease = githubApi("{$ghApiBase}/releases", $ghToken, 'POST', $createPayload);
|
||||||
|
if (!$ghRelease || empty($ghRelease['id'])) {
|
||||||
|
fwrite(STDERR, "Failed to create GitHub release\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$ghReleaseId = $ghRelease['id'];
|
||||||
|
echo " Created GitHub release (id: {$ghReleaseId})\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Step 3: Download assets from Gitea ───────────────────────────────────────
|
||||||
|
|
||||||
|
$tmpDir = sys_get_temp_dir() . '/moko-mirror-' . getmypid();
|
||||||
|
@mkdir($tmpDir, 0755, true);
|
||||||
|
|
||||||
|
$uploadUrl = "{$ghUploadBase}/releases/{$ghReleaseId}/assets";
|
||||||
|
|
||||||
|
foreach ($assets as $asset) {
|
||||||
|
$name = $asset['name'] ?? '';
|
||||||
|
$downloadUrl = $asset['browser_download_url'] ?? '';
|
||||||
|
if ($name === '' || $downloadUrl === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$localPath = "{$tmpDir}/{$name}";
|
||||||
|
echo " Downloading: {$name}\n";
|
||||||
|
|
||||||
|
if (!giteaDownload($downloadUrl, $token, $localPath)) {
|
||||||
|
fwrite(STDERR, " Failed to download: {$name}\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Step 4: Upload asset to GitHub ───────────────────────────────────────
|
||||||
|
echo " Uploading: {$name}\n";
|
||||||
|
$code = githubUploadAsset($uploadUrl, $ghToken, $localPath, $name);
|
||||||
|
$status = ($code >= 200 && $code < 300) ? 'OK' : "FAILED ({$code})";
|
||||||
|
echo " {$status}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cleanup ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
array_map('unlink', glob("{$tmpDir}/*") ?: []);
|
||||||
|
@rmdir($tmpDir);
|
||||||
|
|
||||||
|
// ── Summary ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo "\nMirror complete: {$tag} -> github.com/{$ghRepo}\n";
|
||||||
|
echo " Version: {$version}\n";
|
||||||
|
echo " Assets: " . count($assets) . " file(s)\n";
|
||||||
|
exit(0);
|
||||||
@@ -0,0 +1,582 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/release_package.php
|
||||||
|
* BRIEF: Build packages (ZIP + tar.gz) with SHA-256 and upload to Gitea release
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php release_package.php --path . --version 09.01.00 --tag stable --token TOKEN --api-base URL
|
||||||
|
* php release_package.php --path . --version 09.01.00 --tag development --token TOKEN --api-base URL --repo myrepo
|
||||||
|
*
|
||||||
|
* Builds ZIP and tar.gz packages from src/ or htdocs/, computes SHA-256 checksums,
|
||||||
|
* creates .sha256 sidecar files, and uploads all assets to an existing Gitea release.
|
||||||
|
*
|
||||||
|
* For Joomla packages (type=package with packages/ subdir):
|
||||||
|
* - ZIPs each sub-extension directory
|
||||||
|
* - Copies top-level XML/PHP to package root before archiving
|
||||||
|
*
|
||||||
|
* For standard extensions:
|
||||||
|
* - Builds ZIP and tar.gz from source dir
|
||||||
|
* - Excludes: sftp-config*, .ftpignore, *.ppk, *.pem, *.key, .env*, *.local, .build-trigger
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// ── Argument parsing ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$path = '.';
|
||||||
|
$version = null;
|
||||||
|
$tag = null;
|
||||||
|
$token = null;
|
||||||
|
$apiBase = null;
|
||||||
|
$repoName = '';
|
||||||
|
$outputDir = sys_get_temp_dir();
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--path' && isset($argv[$i + 1])) {
|
||||||
|
$path = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--version' && isset($argv[$i + 1])) {
|
||||||
|
$version = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--tag' && isset($argv[$i + 1])) {
|
||||||
|
$tag = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--token' && isset($argv[$i + 1])) {
|
||||||
|
$token = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--api-base' && isset($argv[$i + 1])) {
|
||||||
|
$apiBase = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--repo' && isset($argv[$i + 1])) {
|
||||||
|
$repoName = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--output' && isset($argv[$i + 1])) {
|
||||||
|
$outputDir = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow token from environment
|
||||||
|
if ($token === null) {
|
||||||
|
$token = getenv('MOKOGITEA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($version === null || $tag === null || $token === null || $apiBase === null) {
|
||||||
|
fwrite(STDERR, "Usage: release_package.php --path . --version VER --tag TAG --token TOKEN --api-base URL\n");
|
||||||
|
fwrite(STDERR, " --repo REPO Repo name for element detection fallback\n");
|
||||||
|
fwrite(STDERR, " --output DIR Output directory for built packages (default: sys_get_temp_dir())\n");
|
||||||
|
fwrite(STDERR, " Token can also be set via MOKOGITEA_TOKEN or GITEA_TOKEN env var\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
|
// ── Helper: Gitea API request ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a Gitea API request.
|
||||||
|
*
|
||||||
|
* @param string $url Full API URL
|
||||||
|
* @param string $token API token
|
||||||
|
* @param string $method HTTP method
|
||||||
|
* @param string|null $body Request body (JSON)
|
||||||
|
*
|
||||||
|
* @return array{data: array<string, mixed>|null, code: int}
|
||||||
|
*/
|
||||||
|
function giteaApiRequest(string $url, string $token, string $method = 'GET', ?string $body = null): array
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
if ($ch === false) {
|
||||||
|
return ['data' => null, 'code' => 0];
|
||||||
|
}
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Content-Type: application/json',
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
]);
|
||||||
|
if ($body !== null) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
|
}
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300 || !is_string($response) || $response === '') {
|
||||||
|
return ['data' => null, 'code' => $httpCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = json_decode($response, true);
|
||||||
|
return ['data' => is_array($decoded) ? $decoded : null, 'code' => $httpCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a file as a release asset.
|
||||||
|
*
|
||||||
|
* @param string $url Upload endpoint URL
|
||||||
|
* @param string $token API token
|
||||||
|
* @param string $filePath Local file path
|
||||||
|
*
|
||||||
|
* @return int HTTP status code
|
||||||
|
*/
|
||||||
|
function giteaUploadAsset(string $url, string $token, string $filePath): int
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
if ($ch === false) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$fileContent = file_get_contents($filePath);
|
||||||
|
if ($fileContent === false) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Content-Type: application/octet-stream',
|
||||||
|
],
|
||||||
|
CURLOPT_POSTFIELDS => $fileContent,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 120,
|
||||||
|
]);
|
||||||
|
curl_exec($ch);
|
||||||
|
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return $httpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Read platform from .mokogitea/manifest.xml ───────────────────────────────
|
||||||
|
|
||||||
|
$detectedPlatform = 'generic';
|
||||||
|
$detectedEntryPoint = '';
|
||||||
|
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
if (file_exists($mokoManifest)) {
|
||||||
|
$mokoXml = @simplexml_load_file($mokoManifest);
|
||||||
|
if ($mokoXml !== false) {
|
||||||
|
$rawPlatform = (string)($mokoXml->governance->platform ?? '');
|
||||||
|
if ($rawPlatform !== '') {
|
||||||
|
$detectedPlatform = match ($rawPlatform) {
|
||||||
|
'waas-component' => 'joomla',
|
||||||
|
'crm-module' => 'dolibarr',
|
||||||
|
default => $rawPlatform,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$detectedEntryPoint = (string)($mokoXml->build->{"entry-point"} ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Detect element metadata from manifest XML ────────────────────────────────
|
||||||
|
|
||||||
|
$extElement = '';
|
||||||
|
$extType = '';
|
||||||
|
$extFolder = '';
|
||||||
|
$typePrefix = '';
|
||||||
|
|
||||||
|
$manifestFiles = array_merge(
|
||||||
|
glob("{$root}/src/pkg_*.xml") ?: [],
|
||||||
|
glob("{$root}/src/*.xml") ?: [],
|
||||||
|
glob("{$root}/*.xml") ?: []
|
||||||
|
);
|
||||||
|
|
||||||
|
$extManifest = null;
|
||||||
|
foreach ($manifestFiles as $file) {
|
||||||
|
$content = file_get_contents($file);
|
||||||
|
if ($content !== false && strpos($content, '<extension') !== false) {
|
||||||
|
$extManifest = $file;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($extManifest !== null) {
|
||||||
|
$xml = file_get_contents($extManifest);
|
||||||
|
if ($xml === false) {
|
||||||
|
$xml = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension type and folder
|
||||||
|
if (preg_match('/type="([^"]*)"/', $xml, $tm)) {
|
||||||
|
$extType = $tm[1];
|
||||||
|
}
|
||||||
|
if (preg_match('/group="([^"]*)"/', $xml, $gm)) {
|
||||||
|
$extFolder = $gm[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element name: <element>, module= attribute, plugin= attribute, <packagename>, or filename
|
||||||
|
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $em)) {
|
||||||
|
$extElement = $em[1];
|
||||||
|
}
|
||||||
|
if ($extElement === '' && preg_match('/module="([^"]*)"/', $xml, $mm)) {
|
||||||
|
$extElement = $mm[1];
|
||||||
|
}
|
||||||
|
if ($extElement === '' && preg_match('/plugin="([^"]*)"/', $xml, $pm)) {
|
||||||
|
$extElement = $pm[1];
|
||||||
|
}
|
||||||
|
// For packages: prefer <packagename> over filename
|
||||||
|
if ($extType === 'package' && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $pn)) {
|
||||||
|
$extElement = $pn[1];
|
||||||
|
}
|
||||||
|
if ($extElement === '') {
|
||||||
|
$extElement = strtolower(basename($extManifest, '.xml'));
|
||||||
|
if (in_array($extElement, ['templatedetails', 'manifest'], true)) {
|
||||||
|
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to repo name
|
||||||
|
if ($extElement === '') {
|
||||||
|
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip existing type prefix to prevent duplication
|
||||||
|
$extElement = (string) preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $extElement);
|
||||||
|
|
||||||
|
// Compute type prefix
|
||||||
|
switch ($extType) {
|
||||||
|
case 'plugin':
|
||||||
|
$typePrefix = "plg_{$extFolder}_";
|
||||||
|
break;
|
||||||
|
case 'module':
|
||||||
|
$typePrefix = 'mod_';
|
||||||
|
break;
|
||||||
|
case 'component':
|
||||||
|
$typePrefix = 'com_';
|
||||||
|
break;
|
||||||
|
case 'template':
|
||||||
|
$typePrefix = 'tpl_';
|
||||||
|
break;
|
||||||
|
case 'library':
|
||||||
|
$typePrefix = 'lib_';
|
||||||
|
break;
|
||||||
|
case 'package':
|
||||||
|
$typePrefix = 'pkg_';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Element: {$typePrefix}{$extElement}\n";
|
||||||
|
echo "Type: {$extType}\n";
|
||||||
|
|
||||||
|
// ── Compute filenames ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$baseName = "{$typePrefix}{$extElement}-{$version}";
|
||||||
|
$zipFile = "{$outputDir}/{$baseName}.zip";
|
||||||
|
$tarFile = "{$outputDir}/{$baseName}.tar.gz";
|
||||||
|
|
||||||
|
echo "ZIP: {$baseName}.zip\n";
|
||||||
|
echo "TAR: {$baseName}.tar.gz\n";
|
||||||
|
|
||||||
|
// ── Find source directory ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$sourceDir = null;
|
||||||
|
|
||||||
|
// Use entry-point from manifest.xml if available
|
||||||
|
if ($detectedEntryPoint !== '') {
|
||||||
|
$entryDir = rtrim(dirname($detectedEntryPoint) === '.' ? $detectedEntryPoint : dirname($detectedEntryPoint), '/');
|
||||||
|
if (is_dir("{$root}/{$entryDir}")) {
|
||||||
|
$sourceDir = "{$root}/{$entryDir}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to common directories
|
||||||
|
if ($sourceDir === null && is_dir("{$root}/src")) {
|
||||||
|
$sourceDir = "{$root}/src";
|
||||||
|
} elseif ($sourceDir === null && is_dir("{$root}/htdocs")) {
|
||||||
|
$sourceDir = "{$root}/htdocs";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sourceDir === null) {
|
||||||
|
echo "No src/ or htdocs/ directory found — skipping package build\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Source: {$sourceDir}\n";
|
||||||
|
|
||||||
|
// ── File exclusion patterns ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** @var array<int, string> */
|
||||||
|
$excludePatterns = [
|
||||||
|
'sftp-config*',
|
||||||
|
'.ftpignore',
|
||||||
|
'*.ppk',
|
||||||
|
'*.pem',
|
||||||
|
'*.key',
|
||||||
|
'.env*',
|
||||||
|
'*.local',
|
||||||
|
'.build-trigger',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a filename matches any exclusion pattern.
|
||||||
|
*
|
||||||
|
* @param string $filename Filename to check
|
||||||
|
* @param array<int,string> $patterns Glob patterns to exclude
|
||||||
|
*
|
||||||
|
* @return bool True if the file should be excluded
|
||||||
|
*/
|
||||||
|
function isExcluded(string $filename, array $patterns): bool
|
||||||
|
{
|
||||||
|
$basename = basename($filename);
|
||||||
|
foreach ($patterns as $pattern) {
|
||||||
|
if (fnmatch($pattern, $basename)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively add files from a directory to a ZipArchive.
|
||||||
|
*
|
||||||
|
* @param ZipArchive $zip ZipArchive instance
|
||||||
|
* @param string $sourceDir Source directory path
|
||||||
|
* @param string $prefix Path prefix inside the archive
|
||||||
|
* @param array<int,string> $excludes Exclusion patterns
|
||||||
|
*/
|
||||||
|
function addDirToZip(ZipArchive $zip, string $sourceDir, string $prefix, array $excludes): void
|
||||||
|
{
|
||||||
|
$iterator = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::LEAVES_ONLY
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if (!$file instanceof SplFileInfo || !$file->isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$realPath = $file->getRealPath();
|
||||||
|
if ($realPath === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExcluded($file->getFilename(), $excludes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativePath = substr($realPath, strlen($sourceDir) + 1);
|
||||||
|
// Normalise to forward slashes for ZIP compatibility
|
||||||
|
$relativePath = str_replace('\\', '/', $relativePath);
|
||||||
|
$archivePath = $prefix !== '' ? "{$prefix}/{$relativePath}" : $relativePath;
|
||||||
|
$zip->addFile($realPath, $archivePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Build packages ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$isJoomlaPackage = ($extType === 'package' && is_dir("{$sourceDir}/packages"));
|
||||||
|
|
||||||
|
if ($isJoomlaPackage) {
|
||||||
|
// ── Joomla package: ZIP each sub-extension, then combine ─────────────────
|
||||||
|
echo "Building Joomla package (sub-extensions)...\n";
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||||
|
fwrite(STDERR, "Failed to create ZIP: {$zipFile}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZIP each sub-extension directory
|
||||||
|
$packageDirs = glob("{$sourceDir}/packages/*", GLOB_ONLYDIR) ?: [];
|
||||||
|
foreach ($packageDirs as $pkgDir) {
|
||||||
|
$subName = basename($pkgDir);
|
||||||
|
$subZipPath = "{$outputDir}/{$subName}.zip";
|
||||||
|
|
||||||
|
$subZip = new ZipArchive();
|
||||||
|
if ($subZip->open($subZipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||||
|
fwrite(STDERR, "Failed to create sub-package ZIP: {$subZipPath}\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addDirToZip($subZip, $pkgDir, '', $excludePatterns);
|
||||||
|
$subZip->close();
|
||||||
|
|
||||||
|
$zip->addFile($subZipPath, "packages/{$subName}.zip");
|
||||||
|
echo " Sub-package: {$subName}.zip\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure package manifest has folder="packages" on <files> element
|
||||||
|
// since sub-packages are stored in a packages/ subdirectory
|
||||||
|
$pkgManifests = glob("{$sourceDir}/pkg_*.xml") ?: [];
|
||||||
|
foreach ($pkgManifests as $pkgXml) {
|
||||||
|
$pkgContent = file_get_contents($pkgXml);
|
||||||
|
if (strpos($pkgContent, '<files>') !== false && strpos($pkgContent, 'folder="packages"') === false) {
|
||||||
|
$pkgContent = str_replace('<files>', '<files folder="packages">', $pkgContent);
|
||||||
|
file_put_contents($pkgXml, $pkgContent);
|
||||||
|
echo " Fixed: added folder=\"packages\" to " . basename($pkgXml) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy top-level XML and PHP files into the package root
|
||||||
|
$topLevelFiles = array_merge(
|
||||||
|
glob("{$sourceDir}/*.xml") ?: [],
|
||||||
|
glob("{$sourceDir}/*.php") ?: []
|
||||||
|
);
|
||||||
|
foreach ($topLevelFiles as $tlFile) {
|
||||||
|
if (!isExcluded(basename($tlFile), $excludePatterns)) {
|
||||||
|
$zip->addFile($tlFile, basename($tlFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include top-level directories (e.g. language/) that aren't packages/
|
||||||
|
$topLevelDirs = glob("{$sourceDir}/*", GLOB_ONLYDIR) ?: [];
|
||||||
|
foreach ($topLevelDirs as $tlDir) {
|
||||||
|
$dirName = basename($tlDir);
|
||||||
|
if ($dirName === 'packages') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addDirToZip($zip, $tlDir, $dirName, $excludePatterns);
|
||||||
|
echo " Included dir: {$dirName}/\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->close();
|
||||||
|
echo "ZIP created: {$zipFile}\n";
|
||||||
|
} else {
|
||||||
|
// ── Standard extension: ZIP from source dir ──────────────────────────────
|
||||||
|
echo "Building standard extension ZIP...\n";
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||||
|
fwrite(STDERR, "Failed to create ZIP: {$zipFile}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
addDirToZip($zip, $sourceDir, '', $excludePatterns);
|
||||||
|
$zip->close();
|
||||||
|
echo "ZIP created: {$zipFile}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Build tar.gz ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$tarExcludeArgs = [];
|
||||||
|
foreach ($excludePatterns as $pattern) {
|
||||||
|
$tarExcludeArgs[] = '--exclude=' . escapeshellarg($pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tarCommand = sprintf(
|
||||||
|
'tar -czf %s -C %s %s .',
|
||||||
|
escapeshellarg($tarFile),
|
||||||
|
escapeshellarg($sourceDir),
|
||||||
|
implode(' ', $tarExcludeArgs)
|
||||||
|
);
|
||||||
|
|
||||||
|
$tarReturnCode = 0;
|
||||||
|
$tarOutputLines = [];
|
||||||
|
exec($tarCommand . ' 2>&1', $tarOutputLines, $tarReturnCode);
|
||||||
|
|
||||||
|
if (!file_exists($tarFile)) {
|
||||||
|
fwrite(STDERR, "Failed to create tar.gz: {$tarFile}\n");
|
||||||
|
if ($tarOutputLines !== []) {
|
||||||
|
fwrite(STDERR, implode("\n", $tarOutputLines) . "\n");
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo "TAR created: {$tarFile}\n";
|
||||||
|
|
||||||
|
// ── Compute SHA-256 checksums ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$zipHash = hash_file('sha256', $zipFile);
|
||||||
|
$tarHash = hash_file('sha256', $tarFile);
|
||||||
|
|
||||||
|
if ($zipHash === false || $tarHash === false) {
|
||||||
|
fwrite(STDERR, "Failed to compute SHA-256 checksums\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zipSha = "{$zipFile}.sha256";
|
||||||
|
$tarSha = "{$tarFile}.sha256";
|
||||||
|
|
||||||
|
file_put_contents($zipSha, "{$zipHash} {$baseName}.zip\n");
|
||||||
|
file_put_contents($tarSha, "{$tarHash} {$baseName}.tar.gz\n");
|
||||||
|
|
||||||
|
echo "SHA-256 (ZIP): {$zipHash}\n";
|
||||||
|
echo "SHA-256 (TAR): {$tarHash}\n";
|
||||||
|
echo "sha256_zip={$zipHash}\n";
|
||||||
|
echo "zip_name={$baseName}.zip\n";
|
||||||
|
|
||||||
|
// Write to GITHUB_OUTPUT if available
|
||||||
|
$ghOutput = getenv('GITHUB_OUTPUT');
|
||||||
|
if ($ghOutput) {
|
||||||
|
file_put_contents($ghOutput, "sha256_zip={$zipHash}\nzip_name={$baseName}.zip\n", FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Get release ID from tag ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$result = giteaApiRequest("{$apiBase}/releases/tags/{$tag}", $token);
|
||||||
|
if ($result['data'] === null || !isset($result['data']['id'])) {
|
||||||
|
fwrite(STDERR, "No release found for tag: {$tag} (HTTP {$result['code']})\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$releaseId = (int) $result['data']['id'];
|
||||||
|
echo "Release ID: {$releaseId} (tag: {$tag})\n";
|
||||||
|
|
||||||
|
// ── Delete existing assets with same names ───────────────────────────────────
|
||||||
|
|
||||||
|
$assetsResult = giteaApiRequest("{$apiBase}/releases/{$releaseId}/assets", $token);
|
||||||
|
$existingAssets = $assetsResult['data'] ?? [];
|
||||||
|
|
||||||
|
$uploadNames = [
|
||||||
|
"{$baseName}.zip",
|
||||||
|
"{$baseName}.tar.gz",
|
||||||
|
"{$baseName}.zip.sha256",
|
||||||
|
"{$baseName}.tar.gz.sha256",
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($existingAssets as $asset) {
|
||||||
|
if (!is_array($asset)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$assetName = $asset['name'] ?? '';
|
||||||
|
$assetId = $asset['id'] ?? 0;
|
||||||
|
if (in_array($assetName, $uploadNames, true) && $assetId > 0) {
|
||||||
|
giteaApiRequest("{$apiBase}/releases/{$releaseId}/assets/{$assetId}", $token, 'DELETE');
|
||||||
|
echo "Deleted existing asset: {$assetName}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Upload assets ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$filesToUpload = [
|
||||||
|
"{$baseName}.zip" => $zipFile,
|
||||||
|
"{$baseName}.tar.gz" => $tarFile,
|
||||||
|
"{$baseName}.zip.sha256" => $zipSha,
|
||||||
|
"{$baseName}.tar.gz.sha256" => $tarSha,
|
||||||
|
];
|
||||||
|
|
||||||
|
$uploaded = 0;
|
||||||
|
foreach ($filesToUpload as $name => $localPath) {
|
||||||
|
if (!file_exists($localPath)) {
|
||||||
|
fwrite(STDERR, "File not found, skipping: {$localPath}\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($name);
|
||||||
|
$httpCode = giteaUploadAsset($uploadUrl, $token, $localPath);
|
||||||
|
$status = ($httpCode >= 200 && $httpCode < 300) ? 'OK' : "FAILED ({$httpCode})";
|
||||||
|
echo "Upload: {$name} — {$status}\n";
|
||||||
|
|
||||||
|
if ($httpCode >= 200 && $httpCode < 300) {
|
||||||
|
$uploaded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Summary ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo "\n";
|
||||||
|
echo "Package build complete\n";
|
||||||
|
echo " Element: {$typePrefix}{$extElement}\n";
|
||||||
|
echo " Version: {$version}\n";
|
||||||
|
echo " Tag: {$tag}\n";
|
||||||
|
echo " Uploaded: {$uploaded}/" . count($filesToUpload) . " asset(s)\n";
|
||||||
|
|
||||||
|
exit($uploaded === count($filesToUpload) ? 0 : 1);
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/release_promote.php
|
||||||
|
* BRIEF: Promote a Gitea release from one channel to another (rename release, tag, assets)
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php release_promote.php --from development --to release-candidate --token TOKEN --api-base URL
|
||||||
|
* php release_promote.php --from release-candidate --to stable --token TOKEN --api-base URL --path .
|
||||||
|
*
|
||||||
|
* When promoting to stable, --path detects extension type prefix for asset renaming.
|
||||||
|
* When --from is "auto", checks beta > alpha > development and uses the first found.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$from = null;
|
||||||
|
$to = null;
|
||||||
|
$token = null;
|
||||||
|
$apiBase = null;
|
||||||
|
$path = '.';
|
||||||
|
$branch = 'main';
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--from' && isset($argv[$i + 1])) {
|
||||||
|
$from = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--to' && isset($argv[$i + 1])) {
|
||||||
|
$to = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--token' && isset($argv[$i + 1])) {
|
||||||
|
$token = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--api-base' && isset($argv[$i + 1])) {
|
||||||
|
$apiBase = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--path' && isset($argv[$i + 1])) {
|
||||||
|
$path = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--branch' && isset($argv[$i + 1])) {
|
||||||
|
$branch = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $token ?: (getenv('MOKOGITEA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: null));
|
||||||
|
|
||||||
|
if ($to === null || $token === null || $apiBase === null) {
|
||||||
|
fwrite(STDERR, "Usage: release_promote.php --from <channel|auto> --to <channel> --token TOKEN --api-base URL [--path .]\n");
|
||||||
|
fwrite(STDERR, " --from auto: checks beta > alpha > development\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Suffix maps ──────────────────────────────────────────────────────────────
|
||||||
|
$suffixMap = [
|
||||||
|
'development' => '-dev',
|
||||||
|
'alpha' => '-alpha',
|
||||||
|
'beta' => '-beta',
|
||||||
|
'release-candidate' => '-rc',
|
||||||
|
'stable' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Channel hierarchy (highest first) ────────────────────────────────────────
|
||||||
|
$channelOrder = ['beta', 'alpha', 'development'];
|
||||||
|
|
||||||
|
// ── Helper: Gitea API request ────────────────────────────────────────────────
|
||||||
|
/** @return array<string, mixed>|null */
|
||||||
|
function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Content-Type: application/json',
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
]);
|
||||||
|
if ($body !== null) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
|
}
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300 || empty($response)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return json_decode($response, true) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function giteaDownload(string $url, string $token, string $dest): bool
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
$fp = fopen($dest, 'wb');
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||||
|
CURLOPT_FILE => $fp,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_TIMEOUT => 120,
|
||||||
|
]);
|
||||||
|
curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
fclose($fp);
|
||||||
|
return $httpCode >= 200 && $httpCode < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Resolve --from auto ──────────────────────────────────────────────────────
|
||||||
|
if ($from === 'auto') {
|
||||||
|
foreach ($channelOrder as $candidate) {
|
||||||
|
$data = giteaApi("{$apiBase}/releases/tags/{$candidate}", $token);
|
||||||
|
if ($data && !empty($data['id'])) {
|
||||||
|
$from = $candidate;
|
||||||
|
echo "Auto-detected source channel: {$from}\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($from === 'auto') {
|
||||||
|
echo "No pre-release found to promote\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Find source release ──────────────────────────────────────────────────────
|
||||||
|
$sourceRelease = giteaApi("{$apiBase}/releases/tags/{$from}", $token);
|
||||||
|
if (!$sourceRelease || empty($sourceRelease['id'])) {
|
||||||
|
fwrite(STDERR, "No release found with tag: {$from}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceId = $sourceRelease['id'];
|
||||||
|
$sourceName = $sourceRelease['name'] ?? '';
|
||||||
|
$sourceBody = $sourceRelease['body'] ?? '';
|
||||||
|
echo "Source: {$from} (id: {$sourceId}) — {$sourceName}\n";
|
||||||
|
|
||||||
|
// ── Get source assets ────────────────────────────────────────────────────────
|
||||||
|
$assets = giteaApi("{$apiBase}/releases/{$sourceId}/assets", $token) ?: [];
|
||||||
|
echo "Assets: " . count($assets) . " file(s)\n";
|
||||||
|
|
||||||
|
// ── Download assets to temp ──────────────────────────────────────────────────
|
||||||
|
$tmpDir = sys_get_temp_dir() . '/moko-promote-' . getmypid();
|
||||||
|
@mkdir($tmpDir, 0755, true);
|
||||||
|
|
||||||
|
foreach ($assets as $asset) {
|
||||||
|
$name = $asset['name'];
|
||||||
|
$downloadUrl = $asset['browser_download_url'];
|
||||||
|
echo " Downloading: {$name}\n";
|
||||||
|
giteaDownload($downloadUrl, $token, "{$tmpDir}/{$name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Detect type prefix for stable promotion ──────────────────────────────────
|
||||||
|
$typePrefix = '';
|
||||||
|
if ($to === 'stable') {
|
||||||
|
$root = realpath($path) ?: $path;
|
||||||
|
$manifestFiles = array_merge(
|
||||||
|
glob("{$root}/src/pkg_*.xml") ?: [],
|
||||||
|
glob("{$root}/src/*.xml") ?: [],
|
||||||
|
glob("{$root}/*.xml") ?: []
|
||||||
|
);
|
||||||
|
foreach ($manifestFiles as $xmlFile) {
|
||||||
|
$xmlContent = file_get_contents($xmlFile);
|
||||||
|
if (strpos($xmlContent, '<extension') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extType = '';
|
||||||
|
$extFolder = '';
|
||||||
|
if (preg_match('/type="([^"]*)"/', $xmlContent, $tm)) {
|
||||||
|
$extType = $tm[1];
|
||||||
|
}
|
||||||
|
if (preg_match('/group="([^"]*)"/', $xmlContent, $gm)) {
|
||||||
|
$extFolder = $gm[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($extType) {
|
||||||
|
case 'plugin':
|
||||||
|
$typePrefix = "plg_{$extFolder}_";
|
||||||
|
break;
|
||||||
|
case 'module':
|
||||||
|
$typePrefix = 'mod_';
|
||||||
|
break;
|
||||||
|
case 'component':
|
||||||
|
$typePrefix = 'com_';
|
||||||
|
break;
|
||||||
|
case 'template':
|
||||||
|
$typePrefix = 'tpl_';
|
||||||
|
break;
|
||||||
|
case 'library':
|
||||||
|
$typePrefix = 'lib_';
|
||||||
|
break;
|
||||||
|
case 'package':
|
||||||
|
$typePrefix = 'pkg_';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($typePrefix !== '') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rename assets ────────────────────────────────────────────────────────────
|
||||||
|
$oldSuffix = $suffixMap[$from] ?? '';
|
||||||
|
$newSuffix = $suffixMap[$to] ?? '';
|
||||||
|
|
||||||
|
$renamedAssets = [];
|
||||||
|
foreach ($assets as $asset) {
|
||||||
|
$oldName = $asset['name'];
|
||||||
|
$newName = $oldName;
|
||||||
|
|
||||||
|
// Strip old suffix
|
||||||
|
if ($oldSuffix !== '') {
|
||||||
|
$newName = str_replace($oldSuffix, '', $newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add type prefix for stable (if not already prefixed)
|
||||||
|
if ($to === 'stable' && $typePrefix !== '' && strpos($newName, $typePrefix) !== 0) {
|
||||||
|
// Strip any existing type prefix to prevent duplication
|
||||||
|
$newName = preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $newName);
|
||||||
|
$newName = $typePrefix . $newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new suffix (for non-stable targets)
|
||||||
|
if ($newSuffix !== '' && strpos($newName, $newSuffix) === false) {
|
||||||
|
// Insert before extension
|
||||||
|
$newName = preg_replace('/(\.(zip|tar\.gz|sha256))$/', $newSuffix . '$1', $newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$renamedAssets[] = ['old' => $oldName, 'new' => $newName];
|
||||||
|
if ($oldName !== $newName) {
|
||||||
|
echo " Rename: {$oldName} → {$newName}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Delete source release + tag ──────────────────────────────────────────────
|
||||||
|
giteaApi("{$apiBase}/releases/{$sourceId}", $token, 'DELETE');
|
||||||
|
giteaApi("{$apiBase}/tags/{$from}", $token, 'DELETE');
|
||||||
|
echo "Deleted source: {$from} release + tag\n";
|
||||||
|
|
||||||
|
// ── Delete existing target release + tag (if any) ────────────────────────────
|
||||||
|
$existingTarget = giteaApi("{$apiBase}/releases/tags/{$to}", $token);
|
||||||
|
if ($existingTarget && !empty($existingTarget['id'])) {
|
||||||
|
giteaApi("{$apiBase}/releases/{$existingTarget['id']}", $token, 'DELETE');
|
||||||
|
giteaApi("{$apiBase}/tags/{$to}", $token, 'DELETE');
|
||||||
|
echo "Deleted existing target: {$to} release + tag\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Create target release ────────────────────────────────────────────────────
|
||||||
|
$isPrerelease = ($to !== 'stable');
|
||||||
|
$newName = preg_replace('/\(' . preg_quote($from, '/') . '\)/', "({$to})", $sourceName);
|
||||||
|
if ($newName === $sourceName) {
|
||||||
|
$newName = str_ireplace($from, $to, $sourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newBody = str_ireplace($from, $to, $sourceBody);
|
||||||
|
|
||||||
|
$payload = json_encode([
|
||||||
|
'tag_name' => $to,
|
||||||
|
'target_commitish' => $branch,
|
||||||
|
'name' => $newName,
|
||||||
|
'body' => $newBody,
|
||||||
|
'prerelease' => $isPrerelease,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newRelease = giteaApi("{$apiBase}/releases", $token, 'POST', $payload);
|
||||||
|
if (!$newRelease || empty($newRelease['id'])) {
|
||||||
|
fwrite(STDERR, "Failed to create {$to} release\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newId = $newRelease['id'];
|
||||||
|
echo "Created: {$to} release (id: {$newId})\n";
|
||||||
|
|
||||||
|
// ── Upload renamed assets ────────────────────────────────────────────────────
|
||||||
|
foreach ($renamedAssets as $entry) {
|
||||||
|
$localFile = "{$tmpDir}/{$entry['old']}";
|
||||||
|
if (!file_exists($localFile)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadName = urlencode($entry['new']);
|
||||||
|
$url = "{$apiBase}/releases/{$newId}/assets?name={$uploadName}";
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Content-Type: application/octet-stream',
|
||||||
|
],
|
||||||
|
CURLOPT_POSTFIELDS => file_get_contents($localFile),
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 120,
|
||||||
|
]);
|
||||||
|
curl_exec($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$status = ($code >= 200 && $code < 300) ? 'OK' : "FAILED ({$code})";
|
||||||
|
echo " Upload: {$entry['new']} — {$status}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cleanup temp ─────────────────────────────────────────────────────────────
|
||||||
|
array_map('unlink', glob("{$tmpDir}/*") ?: []);
|
||||||
|
@rmdir($tmpDir);
|
||||||
|
|
||||||
|
echo "Promoted: {$from} → {$to}\n";
|
||||||
|
exit(0);
|
||||||
+94
-15
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
@@ -26,14 +27,26 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
$path = '.';
|
$path = '.';
|
||||||
$version = null;
|
$version = null;
|
||||||
$platform = 'joomla';
|
$platform = null;
|
||||||
$outputSummary = false;
|
$outputSummary = false;
|
||||||
|
$githubOutput = false;
|
||||||
|
|
||||||
foreach ($argv as $i => $arg) {
|
foreach ($argv as $i => $arg) {
|
||||||
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
if ($arg === '--path' && isset($argv[$i + 1])) {
|
||||||
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
|
$path = $argv[$i + 1];
|
||||||
if ($arg === '--platform' && isset($argv[$i + 1])) $platform = $argv[$i + 1];
|
}
|
||||||
if ($arg === '--output-summary') $outputSummary = true;
|
if ($arg === '--version' && isset($argv[$i + 1])) {
|
||||||
|
$version = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--platform' && isset($argv[$i + 1])) {
|
||||||
|
$platform = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--output-summary') {
|
||||||
|
$outputSummary = true;
|
||||||
|
}
|
||||||
|
if ($arg === '--github-output') {
|
||||||
|
$githubOutput = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($version === null) {
|
if ($version === null) {
|
||||||
@@ -42,17 +55,60 @@ if ($version === null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
|
// Auto-detect platform from manifest.xml if not specified
|
||||||
|
if ($platform === null) {
|
||||||
|
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
if (file_exists($manifestXml)) {
|
||||||
|
$mContent = file_get_contents($manifestXml);
|
||||||
|
if (preg_match('/<platform>([^<]+)<\/platform>/', $mContent, $pm)) {
|
||||||
|
$platform = trim($pm[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Normalize platform aliases
|
||||||
|
if (in_array($platform, ['waas-component'], true)) {
|
||||||
|
$platform = 'joomla';
|
||||||
|
}
|
||||||
|
if (in_array($platform, ['crm-module'], true)) {
|
||||||
|
$platform = 'dolibarr';
|
||||||
|
}
|
||||||
|
if ($platform === null) {
|
||||||
|
$platform = 'generic';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$pass = 0;
|
$pass = 0;
|
||||||
$fail = 0;
|
$fail = 0;
|
||||||
$warn = 0;
|
$warn = 0;
|
||||||
|
/** @var array<int, array{check: string, status: string, details: string}> */
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
function addResult(string $check, string $status, string $details): void {
|
/**
|
||||||
|
* Record a validation result.
|
||||||
|
*
|
||||||
|
* @param string $check Check name
|
||||||
|
* @param string $status PASS, FAIL, or WARN
|
||||||
|
* @param string $details Human-readable details
|
||||||
|
*/
|
||||||
|
function addResult(string $check, string $status, string $details): void
|
||||||
|
{
|
||||||
global $pass, $fail, $warn, $results;
|
global $pass, $fail, $warn, $results;
|
||||||
$results[] = ['check' => $check, 'status' => $status, 'details' => $details];
|
$results[] = ['check' => $check, 'status' => $status, 'details' => $details];
|
||||||
if ($status === 'PASS') $pass++;
|
if ($status === 'PASS') {
|
||||||
elseif ($status === 'FAIL') $fail++;
|
$pass++;
|
||||||
elseif ($status === 'WARN') $warn++;
|
} elseif ($status === 'FAIL') {
|
||||||
|
$fail++;
|
||||||
|
} elseif ($status === 'WARN') {
|
||||||
|
$warn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0. Source directory check
|
||||||
|
$hasSource = is_dir("{$root}/src") || is_dir("{$root}/htdocs");
|
||||||
|
if ($hasSource) {
|
||||||
|
addResult('Source directory', 'PASS', 'src/ or htdocs/ found');
|
||||||
|
} else {
|
||||||
|
addResult('Source directory', 'WARN', 'No src/ or htdocs/ directory');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. README.md exists and contains VERSION
|
// 1. README.md exists and contains VERSION
|
||||||
@@ -60,8 +116,10 @@ if (!file_exists("{$root}/README.md")) {
|
|||||||
addResult('README.md', 'FAIL', 'Not found');
|
addResult('README.md', 'FAIL', 'Not found');
|
||||||
} else {
|
} else {
|
||||||
$readme = file_get_contents("{$root}/README.md");
|
$readme = file_get_contents("{$root}/README.md");
|
||||||
if (preg_match('/VERSION:\s*' . preg_quote($version, '/') . '/', $readme) ||
|
if (
|
||||||
strpos($readme, $version) !== false) {
|
preg_match('/VERSION:\s*' . preg_quote($version, '/') . '/', $readme) ||
|
||||||
|
strpos($readme, $version) !== false
|
||||||
|
) {
|
||||||
addResult('README.md version', 'PASS', "`{$version}` found");
|
addResult('README.md version', 'PASS', "`{$version}` found");
|
||||||
} else {
|
} else {
|
||||||
addResult('README.md version', 'FAIL', "`{$version}` not found in README.md");
|
addResult('README.md version', 'FAIL', "`{$version}` not found in README.md");
|
||||||
@@ -83,7 +141,10 @@ if (!file_exists("{$root}/CHANGELOG.md")) {
|
|||||||
// 3. LICENSE file exists
|
// 3. LICENSE file exists
|
||||||
$licenseFound = false;
|
$licenseFound = false;
|
||||||
foreach (['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'] as $lf) {
|
foreach (['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'] as $lf) {
|
||||||
if (file_exists("{$root}/{$lf}")) { $licenseFound = true; break; }
|
if (file_exists("{$root}/{$lf}")) {
|
||||||
|
$licenseFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
addResult('LICENSE', $licenseFound ? 'PASS' : 'FAIL', $licenseFound ? 'Found' : 'Not found');
|
addResult('LICENSE', $licenseFound ? 'PASS' : 'FAIL', $licenseFound ? 'Found' : 'Not found');
|
||||||
|
|
||||||
@@ -93,7 +154,9 @@ if ($platform === 'joomla') {
|
|||||||
$manifest = null;
|
$manifest = null;
|
||||||
$searchDirs = ["{$root}/src", $root];
|
$searchDirs = ["{$root}/src", $root];
|
||||||
foreach ($searchDirs as $dir) {
|
foreach ($searchDirs as $dir) {
|
||||||
if (!is_dir($dir)) continue;
|
if (!is_dir($dir)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
foreach (glob("{$dir}/*.xml") as $xmlFile) {
|
foreach (glob("{$dir}/*.xml") as $xmlFile) {
|
||||||
$content = file_get_contents($xmlFile);
|
$content = file_get_contents($xmlFile);
|
||||||
if (strpos($content, '<extension') !== false) {
|
if (strpos($content, '<extension') !== false) {
|
||||||
@@ -133,7 +196,10 @@ if ($platform === 'joomla') {
|
|||||||
foreach (['src', 'htdocs'] as $sd) {
|
foreach (['src', 'htdocs'] as $sd) {
|
||||||
$pattern = "{$root}/{$sd}/mod*.class.php";
|
$pattern = "{$root}/{$sd}/mod*.class.php";
|
||||||
$matches = glob($pattern);
|
$matches = glob($pattern);
|
||||||
if (!empty($matches)) { $modFile = $matches[0]; break; }
|
if (!empty($matches)) {
|
||||||
|
$modFile = $matches[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($modFile === null) {
|
if ($modFile === null) {
|
||||||
addResult('Dolibarr mod file', 'FAIL', 'No mod*.class.php found');
|
addResult('Dolibarr mod file', 'FAIL', 'No mod*.class.php found');
|
||||||
@@ -171,7 +237,20 @@ echo $table;
|
|||||||
if ($outputSummary) {
|
if ($outputSummary) {
|
||||||
$summaryFile = getenv('GITHUB_STEP_SUMMARY');
|
$summaryFile = getenv('GITHUB_STEP_SUMMARY');
|
||||||
if ($summaryFile) {
|
if ($summaryFile) {
|
||||||
file_put_contents($summaryFile, "### Pre-Release Validation\n\n{$table}\n", FILE_APPEND);
|
file_put_contents($summaryFile, "## Pre-Release Sanity Checks ({$platform})\n\n{$table}\n", FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($githubOutput) {
|
||||||
|
$ghOutput = getenv('GITHUB_OUTPUT');
|
||||||
|
$lines = [
|
||||||
|
"validation_pass={$pass}",
|
||||||
|
"validation_fail={$fail}",
|
||||||
|
"validation_warn={$warn}",
|
||||||
|
"validation_platform={$platform}",
|
||||||
|
];
|
||||||
|
if ($ghOutput) {
|
||||||
|
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+231
-86
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
@@ -42,15 +43,33 @@ $outputFile = null;
|
|||||||
$githubOutput = false;
|
$githubOutput = false;
|
||||||
|
|
||||||
foreach ($argv as $i => $arg) {
|
foreach ($argv as $i => $arg) {
|
||||||
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
if ($arg === '--path' && isset($argv[$i + 1])) {
|
||||||
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
|
$path = $argv[$i + 1];
|
||||||
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
|
}
|
||||||
if ($arg === '--sha' && isset($argv[$i + 1])) $sha = $argv[$i + 1];
|
if ($arg === '--version' && isset($argv[$i + 1])) {
|
||||||
if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1];
|
$version = $argv[$i + 1];
|
||||||
if ($arg === '--org' && isset($argv[$i + 1])) $org = $argv[$i + 1];
|
}
|
||||||
if ($arg === '--repo' && isset($argv[$i + 1])) $repo = $argv[$i + 1];
|
if ($arg === '--stability' && isset($argv[$i + 1])) {
|
||||||
if ($arg === '--output' && isset($argv[$i + 1])) $outputFile = $argv[$i + 1];
|
$stability = $argv[$i + 1];
|
||||||
if ($arg === '--github-output') $githubOutput = true;
|
}
|
||||||
|
if ($arg === '--sha' && isset($argv[$i + 1])) {
|
||||||
|
$sha = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--gitea-url' && isset($argv[$i + 1])) {
|
||||||
|
$giteaUrl = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--org' && isset($argv[$i + 1])) {
|
||||||
|
$org = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--repo' && isset($argv[$i + 1])) {
|
||||||
|
$repo = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--output' && isset($argv[$i + 1])) {
|
||||||
|
$outputFile = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--github-output') {
|
||||||
|
$githubOutput = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($version === null) {
|
if ($version === null) {
|
||||||
@@ -58,8 +77,33 @@ if ($version === null) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip any existing stability suffix from version (e.g. 01.02.20-dev → 01.02.20)
|
||||||
|
// so per-channel suffixes are applied cleanly without doubling
|
||||||
|
$version = preg_replace('/-(dev|alpha|beta|rc)$/', '', $version);
|
||||||
|
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
|
// -- Read platform from .mokogitea/manifest.xml --------------------------------
|
||||||
|
$detectedPlatform = 'joomla'; // default for backward compat
|
||||||
|
$detectedName = $repo;
|
||||||
|
$detectedPackageType = '';
|
||||||
|
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
if (file_exists($mokoManifest)) {
|
||||||
|
$mokoXml = @simplexml_load_file($mokoManifest);
|
||||||
|
if ($mokoXml !== false) {
|
||||||
|
$rawPlatform = (string)($mokoXml->governance->platform ?? '');
|
||||||
|
if ($rawPlatform !== '') {
|
||||||
|
$detectedPlatform = match ($rawPlatform) {
|
||||||
|
'waas-component' => 'joomla',
|
||||||
|
'crm-module' => 'dolibarr',
|
||||||
|
default => $rawPlatform,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$detectedName = (string)($mokoXml->identity->name ?? $repo);
|
||||||
|
$detectedPackageType = (string)($mokoXml->build->{"package-type"} ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -- Locate Joomla manifest ---------------------------------------------------
|
// -- Locate Joomla manifest ---------------------------------------------------
|
||||||
$manifest = null;
|
$manifest = null;
|
||||||
|
|
||||||
@@ -75,7 +119,9 @@ foreach ($candidates as $f) {
|
|||||||
if ($manifest === null) {
|
if ($manifest === null) {
|
||||||
$searchDirs = ["{$root}/src", "{$root}"];
|
$searchDirs = ["{$root}/src", "{$root}"];
|
||||||
foreach ($searchDirs as $dir) {
|
foreach ($searchDirs as $dir) {
|
||||||
if (!is_dir($dir)) continue;
|
if (!is_dir($dir)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
|
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
|
||||||
if (strpos(file_get_contents($f), '<extension') !== false) {
|
if (strpos(file_get_contents($f), '<extension') !== false) {
|
||||||
$manifest = $f;
|
$manifest = $f;
|
||||||
@@ -85,27 +131,42 @@ if ($manifest === null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($manifest === null) {
|
if ($manifest === null && $detectedPlatform === 'joomla') {
|
||||||
fwrite(STDERR, "No Joomla XML manifest found in {$root}\n");
|
fwrite(STDERR, "No Joomla XML manifest found in {$root}\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Parse extension metadata -------------------------------------------------
|
// -- Parse extension metadata -------------------------------------------------
|
||||||
|
$extName = '';
|
||||||
|
$extType = '';
|
||||||
|
$extElement = '';
|
||||||
|
$extClient = '';
|
||||||
|
$extFolder = '';
|
||||||
|
$targetPlatform = '';
|
||||||
|
$phpMinimum = '';
|
||||||
|
|
||||||
|
if ($manifest !== null) {
|
||||||
|
// Joomla manifest found — parse extension metadata from it
|
||||||
$xml = file_get_contents($manifest);
|
$xml = file_get_contents($manifest);
|
||||||
|
|
||||||
// Extract fields via regex (more portable than SimpleXML for malformed manifests)
|
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $m)) {
|
||||||
$extName = '';
|
$extName = $m[1];
|
||||||
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $m)) $extName = $m[1];
|
}
|
||||||
|
if (preg_match('/<extension[^>]*type="([^"]+)"/', $xml, $m)) {
|
||||||
$extType = '';
|
$extType = $m[1];
|
||||||
if (preg_match('/<extension[^>]*type="([^"]+)"/', $xml, $m)) $extType = $m[1];
|
}
|
||||||
|
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $m)) {
|
||||||
$extElement = '';
|
$extElement = $m[1];
|
||||||
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $m)) $extElement = $m[1];
|
}
|
||||||
// For packages, prefer <packagename> to avoid pkg_pkg_ duplication
|
if (empty($extElement) && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $m)) {
|
||||||
if (empty($extElement) && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $m)) $extElement = $m[1];
|
$extElement = $m[1];
|
||||||
if (empty($extElement) && preg_match('/plugin="([^"]+)"/', $xml, $m)) $extElement = $m[1];
|
}
|
||||||
if (empty($extElement) && preg_match('/module="([^"]+)"/', $xml, $m)) $extElement = $m[1];
|
if (empty($extElement) && preg_match('/plugin="([^"]+)"/', $xml, $m)) {
|
||||||
|
$extElement = $m[1];
|
||||||
|
}
|
||||||
|
if (empty($extElement) && preg_match('/module="([^"]+)"/', $xml, $m)) {
|
||||||
|
$extElement = $m[1];
|
||||||
|
}
|
||||||
if (empty($extElement)) {
|
if (empty($extElement)) {
|
||||||
$fname = strtolower(pathinfo($manifest, PATHINFO_FILENAME));
|
$fname = strtolower(pathinfo($manifest, PATHINFO_FILENAME));
|
||||||
if (in_array($fname, ['templatedetails', 'manifest'])) {
|
if (in_array($fname, ['templatedetails', 'manifest'])) {
|
||||||
@@ -114,23 +175,30 @@ if (empty($extElement)) {
|
|||||||
$extElement = $fname;
|
$extElement = $fname;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Strip existing type prefix to prevent duplication (e.g. pkg_mokowaas → mokowaas)
|
|
||||||
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_\w+_|tpl_|lib_)/', '', $extElement);
|
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_\w+_|tpl_|lib_)/', '', $extElement);
|
||||||
|
|
||||||
$extClient = '';
|
if (preg_match('/<extension[^>]*client="([^"]+)"/', $xml, $m)) {
|
||||||
if (preg_match('/<extension[^>]*client="([^"]+)"/', $xml, $m)) $extClient = $m[1];
|
$extClient = $m[1];
|
||||||
|
}
|
||||||
$extFolder = '';
|
if (preg_match('/<extension[^>]*group="([^"]+)"/', $xml, $m)) {
|
||||||
if (preg_match('/<extension[^>]*group="([^"]+)"/', $xml, $m)) $extFolder = $m[1];
|
$extFolder = $m[1];
|
||||||
|
}
|
||||||
$targetPlatform = '';
|
if (preg_match('/(<targetplatform[^\/]*\/>)/', $xml, $m)) {
|
||||||
if (preg_match('/(<targetplatform[^\/]*\/>)/', $xml, $m)) $targetPlatform = $m[1];
|
$targetPlatform = $m[1];
|
||||||
|
}
|
||||||
if (empty($targetPlatform)) {
|
if (empty($targetPlatform)) {
|
||||||
$targetPlatform = '<targetplatform name="joomla" version="(5|6)\..*" />';
|
$targetPlatform = '<targetplatform name="joomla" version="(5|6)\..*" />';
|
||||||
}
|
}
|
||||||
|
if (preg_match('/<php_minimum>([^<]+)<\/php_minimum>/', $xml, $m)) {
|
||||||
$phpMinimum = '';
|
$phpMinimum = $m[1];
|
||||||
if (preg_match('/<php_minimum>([^<]+)<\/php_minimum>/', $xml, $m)) $phpMinimum = $m[1];
|
}
|
||||||
|
} else {
|
||||||
|
// Non-Joomla platform — derive metadata from .mokogitea/manifest.xml
|
||||||
|
$extName = $detectedName ?: ($repo ?: basename($root));
|
||||||
|
$extElement = strtolower(str_replace([' ', '-'], '', $extName));
|
||||||
|
$extType = $detectedPackageType ?: 'generic';
|
||||||
|
$targetPlatform = "<targetplatform name=\"{$detectedPlatform}\" version=\".*\" />";
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve language key names (e.g. PLG_SYSTEM_MOKOJOOMTOS)
|
// Resolve language key names (e.g. PLG_SYSTEM_MOKOJOOMTOS)
|
||||||
if (preg_match('/^[A-Z_]+$/', $extName)) {
|
if (preg_match('/^[A-Z_]+$/', $extName)) {
|
||||||
@@ -153,18 +221,55 @@ if (preg_match('/^[A-Z_]+$/', $extName)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallbacks
|
// Fallbacks
|
||||||
if (empty($extName)) $extName = $repo ?: basename($root);
|
if (empty($extName)) {
|
||||||
if (empty($extType)) $extType = 'component';
|
$extName = $repo ?: basename($root);
|
||||||
|
}
|
||||||
|
if (empty($extType)) {
|
||||||
|
$extType = 'component';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If extName is still a technical/prefixed name (mod_foo, tpl_foo), prefer
|
||||||
|
// the human-readable name from .mokogitea/manifest.xml <identity><name>
|
||||||
|
if (preg_match('/^(pkg_|com_|mod_|plg_\w+_|tpl_|lib_)/i', $extName) && !empty($detectedName)) {
|
||||||
|
$extName = $detectedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build display name with type prefix (e.g. "Package - MokoWaaS")
|
||||||
|
$typeDisplayMap = [
|
||||||
|
'package' => 'Package',
|
||||||
|
'plugin' => 'Plugin',
|
||||||
|
'module' => 'Module',
|
||||||
|
'component' => 'Component',
|
||||||
|
'template' => 'Template',
|
||||||
|
'library' => 'Library',
|
||||||
|
'file' => 'File',
|
||||||
|
];
|
||||||
|
$typeDisplay = $typeDisplayMap[$extType] ?? ucfirst($extType);
|
||||||
|
// Strip existing type prefix to avoid doubling (e.g. "Template - Template - MokoOnyx")
|
||||||
|
$extName = preg_replace('/^(' . implode('|', array_map('preg_quote', $typeDisplayMap)) . ')\s*-\s*/i', '', $extName);
|
||||||
|
$displayName = "{$typeDisplay} - {$extName}";
|
||||||
|
|
||||||
// -- Build type prefix --------------------------------------------------------
|
// -- Build type prefix --------------------------------------------------------
|
||||||
$typePrefix = '';
|
$typePrefix = '';
|
||||||
switch ($extType) {
|
switch ($extType) {
|
||||||
case 'plugin': $typePrefix = "plg_{$extFolder}_"; break;
|
case 'plugin':
|
||||||
case 'module': $typePrefix = 'mod_'; break;
|
$typePrefix = "plg_{$extFolder}_";
|
||||||
case 'component': $typePrefix = 'com_'; break;
|
break;
|
||||||
case 'template': $typePrefix = 'tpl_'; break;
|
case 'module':
|
||||||
case 'library': $typePrefix = 'lib_'; break;
|
$typePrefix = 'mod_';
|
||||||
case 'package': $typePrefix = 'pkg_'; break;
|
break;
|
||||||
|
case 'component':
|
||||||
|
$typePrefix = 'com_';
|
||||||
|
break;
|
||||||
|
case 'template':
|
||||||
|
$typePrefix = 'tpl_';
|
||||||
|
break;
|
||||||
|
case 'library':
|
||||||
|
$typePrefix = 'lib_';
|
||||||
|
break;
|
||||||
|
case 'package':
|
||||||
|
$typePrefix = 'pkg_';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Export to GITHUB_OUTPUT if requested -------------------------------------
|
// -- Export to GITHUB_OUTPUT if requested -------------------------------------
|
||||||
@@ -181,7 +286,9 @@ if ($githubOutput) {
|
|||||||
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
|
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
|
||||||
fwrite(STDERR, "Exported " . count($lines) . " fields to GITHUB_OUTPUT\n");
|
fwrite(STDERR, "Exported " . count($lines) . " fields to GITHUB_OUTPUT\n");
|
||||||
} else {
|
} else {
|
||||||
foreach ($lines as $line) echo "{$line}\n";
|
foreach ($lines as $line) {
|
||||||
|
echo "{$line}\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,29 +301,35 @@ $stabilitySuffixMap = [
|
|||||||
'development' => '-dev',
|
'development' => '-dev',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Joomla <tags><tag> values — maps to Joomla's stabilityTagToInteger()
|
||||||
$stabilityTagMap = [
|
$stabilityTagMap = [
|
||||||
'stable' => 'stable',
|
'stable' => 'stable',
|
||||||
'rc' => 'rc',
|
'rc' => 'rc',
|
||||||
'beta' => 'beta',
|
'beta' => 'beta',
|
||||||
'alpha' => 'alpha',
|
'alpha' => 'alpha',
|
||||||
|
'development' => 'dev',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Gitea release tag names (used in download/info URLs)
|
||||||
|
$releaseTagMap = [
|
||||||
|
'stable' => 'stable',
|
||||||
|
'rc' => 'release-candidate',
|
||||||
|
'beta' => 'beta',
|
||||||
|
'alpha' => 'alpha',
|
||||||
'development' => 'development',
|
'development' => 'development',
|
||||||
];
|
];
|
||||||
|
|
||||||
// -- Build update entries -----------------------------------------------------
|
// -- Build update entries -----------------------------------------------------
|
||||||
$releaseTag = $stabilityTagMap[$stability] ?? $stability;
|
|
||||||
|
|
||||||
// For the primary entry: apply suffix if not stable
|
// For the primary entry: apply suffix if not stable
|
||||||
$primarySuffix = $stabilitySuffixMap[$stability] ?? '';
|
$primarySuffix = $stabilitySuffixMap[$stability] ?? '';
|
||||||
$primaryVersion = $version . $primarySuffix;
|
$primaryVersion = $version . $primarySuffix;
|
||||||
|
|
||||||
$downloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$releaseTag}/{$typePrefix}{$extElement}-{$primaryVersion}.zip";
|
// Build client tag — Joomla requires <client>site</client> to match updates
|
||||||
$infoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$releaseTag}";
|
// to installed extensions. Without it, extension_id=0 in #__updates.
|
||||||
|
|
||||||
// Build client tag
|
|
||||||
$clientTag = '';
|
$clientTag = '';
|
||||||
if (!empty($extClient)) {
|
if (!empty($extClient)) {
|
||||||
$clientTag = " <client>{$extClient}</client>";
|
$clientTag = " <client>{$extClient}</client>";
|
||||||
} elseif ($extType === 'module' || $extType === 'plugin') {
|
} else {
|
||||||
$clientTag = ' <client>site</client>';
|
$clientTag = ' <client>site</client>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +358,8 @@ function buildEntry(
|
|||||||
string $tagName,
|
string $tagName,
|
||||||
string $entryVersion,
|
string $entryVersion,
|
||||||
string $entryDownloadUrl,
|
string $entryDownloadUrl,
|
||||||
string $extName,
|
string $displayName,
|
||||||
|
string $stabilityLabel,
|
||||||
string $extElement,
|
string $extElement,
|
||||||
string $extType,
|
string $extType,
|
||||||
string $clientTag,
|
string $clientTag,
|
||||||
@@ -257,57 +371,85 @@ function buildEntry(
|
|||||||
): string {
|
): string {
|
||||||
$lines = [];
|
$lines = [];
|
||||||
$lines[] = ' <update>';
|
$lines[] = ' <update>';
|
||||||
$lines[] = " <name>{$extName}</name>";
|
$lines[] = " <name>{$displayName}</name>";
|
||||||
$lines[] = " <description>{$extName} update</description>";
|
$lines[] = " <description>{$displayName} {$stabilityLabel} build.</description>";
|
||||||
// Element in updates.xml must match what Joomla stores in #__extensions
|
// Element in updates.xml must match what Joomla stores in #__extensions.
|
||||||
// For packages: pkg_elementname. For plugins: elementname (folder handles grouping).
|
// Plugins are stored as bare element (folder handles grouping).
|
||||||
$dbElement = ($extType === 'package') ? "pkg_{$extElement}" : $extElement;
|
// All other types need their prefix: mod_, com_, tpl_, pkg_, lib_.
|
||||||
|
$prefixMap = [
|
||||||
|
'package' => 'pkg_',
|
||||||
|
'module' => 'mod_',
|
||||||
|
'component' => 'com_',
|
||||||
|
'template' => 'tpl_',
|
||||||
|
'library' => 'lib_',
|
||||||
|
];
|
||||||
|
$dbElement = isset($prefixMap[$extType]) ? $prefixMap[$extType] . $extElement : $extElement;
|
||||||
$lines[] = " <element>{$dbElement}</element>";
|
$lines[] = " <element>{$dbElement}</element>";
|
||||||
$lines[] = " <type>{$extType}</type>";
|
$lines[] = " <type>{$extType}</type>";
|
||||||
|
$lines[] = $clientTag;
|
||||||
$lines[] = " <version>{$entryVersion}</version>";
|
$lines[] = " <version>{$entryVersion}</version>";
|
||||||
if (!empty($clientTag)) $lines[] = $clientTag;
|
$lines[] = " <creationDate>" . date('Y-m-d') . "</creationDate>";
|
||||||
if (!empty($folderTag)) $lines[] = $folderTag;
|
if (!empty($folderTag)) {
|
||||||
$lines[] = " <tags><tag>{$tagName}</tag></tags>";
|
$lines[] = $folderTag;
|
||||||
$lines[] = " <infourl title=\"{$extName}\">{$infoUrl}</infourl>";
|
}
|
||||||
|
$lines[] = " <infourl title='{$displayName}'>{$infoUrl}</infourl>";
|
||||||
$lines[] = ' <downloads>';
|
$lines[] = ' <downloads>';
|
||||||
$lines[] = " <downloadurl type=\"full\" format=\"zip\">{$entryDownloadUrl}</downloadurl>";
|
$lines[] = " <downloadurl type='full' format='zip'>{$entryDownloadUrl}</downloadurl>";
|
||||||
$lines[] = ' </downloads>';
|
$lines[] = ' </downloads>';
|
||||||
if (!empty($shaTag)) $lines[] = $shaTag;
|
if (!empty($shaTag)) {
|
||||||
$lines[] = " {$targetPlatform}";
|
$lines[] = $shaTag;
|
||||||
if (!empty($phpTag)) $lines[] = $phpTag;
|
}
|
||||||
|
$lines[] = " <tags><tag>{$tagName}</tag></tags>";
|
||||||
$lines[] = ' <maintainer>Moko Consulting</maintainer>';
|
$lines[] = ' <maintainer>Moko Consulting</maintainer>';
|
||||||
$lines[] = ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>';
|
$lines[] = ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>';
|
||||||
|
$lines[] = " {$targetPlatform}";
|
||||||
|
if (!empty($phpTag)) {
|
||||||
|
$lines[] = $phpTag;
|
||||||
|
}
|
||||||
$lines[] = ' </update>';
|
$lines[] = ' </update>';
|
||||||
return implode("\n", $lines);
|
return implode("\n", $lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Determine which channels to write ----------------------------------------
|
// -- Determine which channels to write ----------------------------------------
|
||||||
// Stable cascades to all channels; pre-releases only write their level and below
|
// Stable cascades to all channels; pre-releases cascade down to lower channels.
|
||||||
// Each channel gets its own suffixed version:
|
// Each channel entry represents "latest release available at this stability or higher".
|
||||||
// development -> 04.01.00-dev
|
// When stable releases, ALL channels point to stable (it's the newest for everyone).
|
||||||
// alpha -> 04.01.00-alpha
|
// When RC releases, rc/beta/alpha/dev point to RC; stable is preserved.
|
||||||
// beta -> 04.01.00-beta
|
// When dev releases, only dev is updated; everything else is preserved.
|
||||||
// rc -> 04.01.00-rc
|
|
||||||
// stable -> 04.01.00
|
|
||||||
$allChannels = ['development', 'alpha', 'beta', 'rc', 'stable'];
|
$allChannels = ['development', 'alpha', 'beta', 'rc', 'stable'];
|
||||||
$stabilityIndex = array_search($stability === 'development' ? 'development' : $stability, $allChannels);
|
$stabilityIndex = array_search($stability === 'development' ? 'development' : $stability, $allChannels);
|
||||||
if ($stabilityIndex === false) $stabilityIndex = 4; // default to stable
|
if ($stabilityIndex === false) {
|
||||||
|
$stabilityIndex = 4; // default to stable
|
||||||
|
}
|
||||||
|
|
||||||
// Write only the current channel entry (not cascade)
|
// Write entries for the current channel AND all lower channels (cascade down)
|
||||||
// Each channel release only creates its own entry; preserved entries handle other channels
|
// All cascaded entries point to the CURRENT release (the highest stability being built)
|
||||||
$entries = [];
|
$entries = [];
|
||||||
$channelName = $allChannels[$stabilityIndex];
|
$giteaTag = $releaseTagMap[$stability] ?? $stability;
|
||||||
$channelSuffix = $stabilitySuffixMap[$channelName] ?? '';
|
$channelVersion = $version . ($stabilitySuffixMap[$stability] ?? '');
|
||||||
$channelVersion = $version . $channelSuffix;
|
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$giteaTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
|
||||||
$channelTag = $stabilityTagMap[$channelName] ?? $channelName;
|
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$giteaTag}";
|
||||||
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$channelTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
|
|
||||||
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$channelTag}";
|
// Stability labels for descriptions
|
||||||
|
$stabilityLabelMap = [
|
||||||
|
'stable' => 'stable',
|
||||||
|
'rc' => 'rc',
|
||||||
|
'beta' => 'beta',
|
||||||
|
'alpha' => 'alpha',
|
||||||
|
'development' => 'development',
|
||||||
|
];
|
||||||
|
|
||||||
|
for ($i = 0; $i <= $stabilityIndex; $i++) {
|
||||||
|
$channelName = $allChannels[$i];
|
||||||
|
$joomlaTag = $stabilityTagMap[$channelName] ?? $channelName;
|
||||||
|
$stabilityLabel = $stabilityLabelMap[$channelName] ?? $channelName;
|
||||||
|
|
||||||
$entries[] = buildEntry(
|
$entries[] = buildEntry(
|
||||||
$channelName,
|
$joomlaTag,
|
||||||
$channelVersion,
|
$channelVersion,
|
||||||
$channelDownloadUrl,
|
$channelDownloadUrl,
|
||||||
$extName,
|
$displayName,
|
||||||
|
$stabilityLabel,
|
||||||
$extElement,
|
$extElement,
|
||||||
$extType,
|
$extType,
|
||||||
$clientTag,
|
$clientTag,
|
||||||
@@ -317,6 +459,7 @@ $entries[] = buildEntry(
|
|||||||
$phpTag,
|
$phpTag,
|
||||||
$shaTag
|
$shaTag
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// -- Preserve existing entries for channels not being updated -----------------
|
// -- Preserve existing entries for channels not being updated -----------------
|
||||||
$dest = $outputFile ?? "{$root}/updates.xml";
|
$dest = $outputFile ?? "{$root}/updates.xml";
|
||||||
@@ -325,11 +468,13 @@ $preservedEntries = [];
|
|||||||
if (file_exists($dest)) {
|
if (file_exists($dest)) {
|
||||||
$existingXml = @simplexml_load_file($dest);
|
$existingXml = @simplexml_load_file($dest);
|
||||||
if ($existingXml) {
|
if ($existingXml) {
|
||||||
// Channels we're writing — don't preserve these
|
// Joomla tags we're writing — don't preserve these
|
||||||
$writtenChannels = [];
|
$writtenChannels = [];
|
||||||
for ($i = 0; $i <= $stabilityIndex; $i++) {
|
for ($i = 0; $i <= $stabilityIndex; $i++) {
|
||||||
$writtenChannels[] = $allChannels[$i];
|
$writtenChannels[] = $stabilityTagMap[$allChannels[$i]] ?? $allChannels[$i];
|
||||||
}
|
}
|
||||||
|
// Also match legacy/alternate tag names (e.g. 'development' = 'dev')
|
||||||
|
$writtenChannels[] = 'development'; // alias for 'dev'
|
||||||
|
|
||||||
foreach ($existingXml->update as $existingUpdate) {
|
foreach ($existingXml->update as $existingUpdate) {
|
||||||
$existingTag = '';
|
$existingTag = '';
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
* --branches Comma-separated target branches to sync to (default: main,dev)
|
* --branches Comma-separated target branches to sync to (default: main,dev)
|
||||||
* --current Current branch to skip (required)
|
* --current Current branch to skip (required)
|
||||||
* --version Version string for commit message (optional)
|
* --version Version string for commit message (optional)
|
||||||
* --token Gitea API token (default: env GA_TOKEN)
|
* --token Gitea API token (default: env MOKOGITEA_TOKEN)
|
||||||
* --gitea-url Gitea instance URL (default: env GITEA_URL or https://git.mokoconsulting.tech)
|
* --gitea-url Gitea instance URL (default: env GITEA_URL or https://git.mokoconsulting.tech)
|
||||||
* --org Organization (default: env GITEA_ORG)
|
* --org Organization (default: env GITEA_ORG)
|
||||||
* --repo Repository name (default: env GITEA_REPO)
|
* --repo Repository name (default: env GITEA_REPO)
|
||||||
@@ -37,7 +37,7 @@ $path = '.';
|
|||||||
$branches = 'main,dev';
|
$branches = 'main,dev';
|
||||||
$current = '';
|
$current = '';
|
||||||
$version = '';
|
$version = '';
|
||||||
$token = getenv('GA_TOKEN') ?: '';
|
$token = getenv('MOKOGITEA_TOKEN') ?: '';
|
||||||
$giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
$giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
||||||
$org = getenv('GITEA_ORG') ?: '';
|
$org = getenv('GITEA_ORG') ?: '';
|
||||||
$repo = getenv('GITEA_REPO') ?: '';
|
$repo = getenv('GITEA_REPO') ?: '';
|
||||||
@@ -59,7 +59,7 @@ if ($current === '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
fwrite(STDERR, "Error: --token or GA_TOKEN env is required\n");
|
fwrite(STDERR, "Error: --token or MOKOGITEA_TOKEN env is required\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+115
-20
@@ -9,7 +9,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/version_bump.php
|
* PATH: /cli/version_bump.php
|
||||||
* BRIEF: Auto-increment patch version — checks both README.md and manifest XML, uses the higher version as base
|
* BRIEF: Auto-increment version — manifest.xml is canonical, cascades to all XML and MD files
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -24,7 +24,20 @@ foreach ($argv as $i => $arg) {
|
|||||||
|
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
// ── Read version from README.md ──────────────────────────────────────────────
|
// -- 1. Read version from .mokogitea/manifest.xml (canonical) --
|
||||||
|
$mokoVersion = null;
|
||||||
|
$mokoSuffix = '';
|
||||||
|
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
$mokoContent = '';
|
||||||
|
if (file_exists($mokoManifest)) {
|
||||||
|
$mokoContent = file_get_contents($mokoManifest);
|
||||||
|
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(?:-((?:(?:dev|alpha|beta|rc)-?)+))?</version>|', $mokoContent, $m)) {
|
||||||
|
$mokoVersion = $m[1];
|
||||||
|
$mokoSuffix = isset($m[2]) ? $m[2] : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 2. Fallback: README.md --
|
||||||
$readmeVersion = null;
|
$readmeVersion = null;
|
||||||
$readme = "{$root}/README.md";
|
$readme = "{$root}/README.md";
|
||||||
$readmeContent = '';
|
$readmeContent = '';
|
||||||
@@ -35,10 +48,9 @@ if (file_exists($readme)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Read version from Joomla manifest XML ────────────────────────────────────
|
// -- 3. Fallback: Joomla manifest XML --
|
||||||
$manifestVersion = null;
|
$manifestVersion = null;
|
||||||
|
$manifestSuffix = '';
|
||||||
// Check package manifest first (pkg_*.xml), then sub-extension manifests
|
|
||||||
$manifestFiles = array_merge(
|
$manifestFiles = array_merge(
|
||||||
glob("{$root}/src/pkg_*.xml") ?: [],
|
glob("{$root}/src/pkg_*.xml") ?: [],
|
||||||
glob("{$root}/src/*.xml") ?: [],
|
glob("{$root}/src/*.xml") ?: [],
|
||||||
@@ -52,31 +64,31 @@ foreach ($manifestFiles as $xmlFile) {
|
|||||||
if (strpos($xmlContent, '<extension') === false && strpos($xmlContent, '<version>') === false) {
|
if (strpos($xmlContent, '<extension') === false && strpos($xmlContent, '<version>') === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(?:-[a-z]+)?</version>|', $xmlContent, $xm)) {
|
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})((?:-(?:dev|alpha|beta|rc))+)?</version>|', $xmlContent, $xm)) {
|
||||||
$candidate = $xm[1];
|
$candidate = $xm[1];
|
||||||
if ($manifestVersion === null || version_compare($candidate, $manifestVersion, '>')) {
|
if ($manifestVersion === null || version_compare($candidate, $manifestVersion, '>')) {
|
||||||
$manifestVersion = $candidate;
|
$manifestVersion = $candidate;
|
||||||
|
// Preserve the suffix from the manifest (e.g. dev, rc) — strip leading dash
|
||||||
|
$manifestSuffix = ltrim($xm[2] ?? '', '-');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Use the higher version as base ───────────────────────────────────────────
|
// -- Use the highest version as base --
|
||||||
$baseVersion = null;
|
$baseVersion = null;
|
||||||
|
$candidates = array_filter([$mokoVersion, $readmeVersion, $manifestVersion]);
|
||||||
if ($readmeVersion !== null && $manifestVersion !== null) {
|
foreach ($candidates as $v) {
|
||||||
$baseVersion = version_compare($manifestVersion, $readmeVersion, '>') ? $manifestVersion : $readmeVersion;
|
if ($baseVersion === null || version_compare($v, $baseVersion, '>')) {
|
||||||
} elseif ($manifestVersion !== null) {
|
$baseVersion = $v;
|
||||||
$baseVersion = $manifestVersion;
|
}
|
||||||
} elseif ($readmeVersion !== null) {
|
|
||||||
$baseVersion = $readmeVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($baseVersion === null) {
|
if ($baseVersion === null) {
|
||||||
fwrite(STDERR, "No version found in README.md or manifest XML\n");
|
fwrite(STDERR, "No version found in manifest.xml, README.md, or Joomla XML\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Parse and bump ───────────────────────────────────────────────────────────
|
// -- Parse and bump --
|
||||||
if (!preg_match('/^(\d{2})\.(\d{2})\.(\d{2})$/', $baseVersion, $parts)) {
|
if (!preg_match('/^(\d{2})\.(\d{2})\.(\d{2})$/', $baseVersion, $parts)) {
|
||||||
fwrite(STDERR, "Invalid version format: {$baseVersion}\n");
|
fwrite(STDERR, "Invalid version format: {$baseVersion}\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -99,16 +111,99 @@ switch ($type) {
|
|||||||
|
|
||||||
$new = sprintf('%02d.%02d.%02d', $major, $minor, $patch);
|
$new = sprintf('%02d.%02d.%02d', $major, $minor, $patch);
|
||||||
|
|
||||||
// ── Update README.md ─────────────────────────────────────────────────────────
|
// -- Write clean version (no suffix) ------------------------------------------
|
||||||
|
// Suffixes (-dev, -alpha, -beta, -rc) are managed by version_set_platform.php
|
||||||
|
// called from CI workflows with the appropriate --stability flag. version_bump
|
||||||
|
// always writes a clean base version so the suffix layer stays consistent.
|
||||||
|
$newFull = $new;
|
||||||
|
|
||||||
|
// -- Update .mokogitea/manifest.xml (canonical — preserves suffix) --
|
||||||
|
if (file_exists($mokoManifest) && !empty($mokoContent)) {
|
||||||
|
$updated = preg_replace(
|
||||||
|
'|<version>\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?</version>|',
|
||||||
|
"<version>{$newFull}</version>",
|
||||||
|
$mokoContent,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
if ($updated !== null) {
|
||||||
|
file_put_contents($mokoManifest, $updated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Update README.md --
|
||||||
if (file_exists($readme) && !empty($readmeContent)) {
|
if (file_exists($readme) && !empty($readmeContent)) {
|
||||||
$updated = preg_replace(
|
$updated = preg_replace(
|
||||||
'/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}/m',
|
'/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?/m',
|
||||||
'${1}' . $new,
|
'${1}' . $newFull,
|
||||||
$readmeContent,
|
$readmeContent,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
if ($updated !== null) {
|
||||||
file_put_contents($readme, $updated);
|
file_put_contents($readme, $updated);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
echo "{$old} → {$new}\n";
|
// -- Cascade to ALL Joomla extension XML manifests --
|
||||||
|
$xmlPatterns = [
|
||||||
|
"{$root}/src/pkg_*.xml",
|
||||||
|
"{$root}/src/*.xml",
|
||||||
|
"{$root}/src/packages/*/*.xml",
|
||||||
|
"{$root}/*.xml",
|
||||||
|
];
|
||||||
|
|
||||||
|
$updatedFiles = [];
|
||||||
|
foreach ($xmlPatterns as $pattern) {
|
||||||
|
foreach (glob($pattern) ?: [] as $xmlFile) {
|
||||||
|
$content = file_get_contents($xmlFile);
|
||||||
|
// Only update files that have an <extension> tag (Joomla manifests)
|
||||||
|
if (strpos($content, '<extension') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$newContent = preg_replace(
|
||||||
|
'|<version>\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?</version>|',
|
||||||
|
"<version>{$newFull}</version>",
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
if ($newContent !== null && $newContent !== $content) {
|
||||||
|
file_put_contents($xmlFile, $newContent);
|
||||||
|
$updatedFiles[] = substr($xmlFile, strlen($root) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($updatedFiles)) {
|
||||||
|
fwrite(STDERR, "Updated " . count($updatedFiles) . " Joomla manifest(s): " . implode(', ', $updatedFiles) . "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Update package.json (Node.js / MCP) --
|
||||||
|
$packageJsonFile = "{$root}/package.json";
|
||||||
|
if (file_exists($packageJsonFile)) {
|
||||||
|
$pkgContent = file_get_contents($packageJsonFile);
|
||||||
|
$updatedPkg = preg_replace(
|
||||||
|
'/("version"\s*:\s*")\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?(")/m',
|
||||||
|
'${1}' . $newFull . '${2}',
|
||||||
|
$pkgContent
|
||||||
|
);
|
||||||
|
if ($updatedPkg !== $pkgContent) {
|
||||||
|
file_put_contents($packageJsonFile, $updatedPkg);
|
||||||
|
fwrite(STDERR, "Updated package.json\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Update pyproject.toml (Python) --
|
||||||
|
$pyprojectFile = "{$root}/pyproject.toml";
|
||||||
|
if (file_exists($pyprojectFile)) {
|
||||||
|
$pyContent = file_get_contents($pyprojectFile);
|
||||||
|
$updatedPy = preg_replace(
|
||||||
|
'/^(version\s*=\s*")\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?(")/m',
|
||||||
|
'${1}' . $newFull . '${2}',
|
||||||
|
$pyContent
|
||||||
|
);
|
||||||
|
if ($updatedPy !== $pyContent) {
|
||||||
|
file_put_contents($pyprojectFile, $updatedPy);
|
||||||
|
fwrite(STDERR, "Updated pyproject.toml\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "{$old} -> {$newFull}\n";
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
* --path Repository root (reads current version from local manifest)
|
* --path Repository root (reads current version from local manifest)
|
||||||
* --branch Target branch to bump (required, e.g. dev)
|
* --branch Target branch to bump (required, e.g. dev)
|
||||||
* --bump Bump type: patch | minor | major (default: minor)
|
* --bump Bump type: patch | minor | major (default: minor)
|
||||||
* --token Gitea API token (or GA_TOKEN env var)
|
* --token Gitea API token (or MOKOGITEA_TOKEN env var)
|
||||||
* --api-base Gitea API base URL for the repo
|
* --api-base Gitea API base URL for the repo
|
||||||
* --no-changelog Skip CHANGELOG.md bump
|
* --no-changelog Skip CHANGELOG.md bump
|
||||||
* --repo Repository path (owner/repo) for API base construction
|
* --repo Repository path (owner/repo) for API base construction
|
||||||
@@ -49,7 +49,7 @@ foreach ($argv as $i => $arg) {
|
|||||||
if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1];
|
if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($token === null) $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
if ($token === null) $token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
||||||
if ($giteaUrl === null) $giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
if ($giteaUrl === null) $giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
||||||
|
|
||||||
if ($apiBase === null && $repo !== null) {
|
if ($apiBase === null && $repo !== null) {
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/version_check.php
|
||||||
|
* VERSION: 05.00.00
|
||||||
|
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php version_check.php --path /repo
|
||||||
|
* php version_check.php --path /repo --strict # exit 1 on mismatch
|
||||||
|
* php version_check.php --path /repo --fix # fix mismatches to highest version
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$path = '.';
|
||||||
|
$strict = false;
|
||||||
|
$fix = false;
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
||||||
|
if ($arg === '--strict') $strict = true;
|
||||||
|
if ($arg === '--fix') $fix = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$root = realpath($path) ?: $path;
|
||||||
|
$errors = 0;
|
||||||
|
$versions = [];
|
||||||
|
|
||||||
|
// ── Read README.md version ───────────────────────────────────────────────────
|
||||||
|
$readme = "{$root}/README.md";
|
||||||
|
if (file_exists($readme)) {
|
||||||
|
$content = file_get_contents($readme);
|
||||||
|
if (preg_match('/VERSION:\s*(\d{2}\.\d{2}\.\d{2})/m', $content, $m)) {
|
||||||
|
$versions['README.md'] = $m[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Read manifest XML versions ───────────────────────────────────────────────
|
||||||
|
$xmlGlobs = [
|
||||||
|
"{$root}/src/pkg_*.xml",
|
||||||
|
"{$root}/src/*.xml",
|
||||||
|
"{$root}/src/packages/*/*.xml",
|
||||||
|
"{$root}/*.xml",
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($xmlGlobs as $glob) {
|
||||||
|
foreach (glob($glob) ?: [] as $file) {
|
||||||
|
// Skip updates.xml
|
||||||
|
if (basename($file) === 'updates.xml') continue;
|
||||||
|
|
||||||
|
$xmlContent = file_get_contents($file);
|
||||||
|
if (strpos($xmlContent, '<extension') === false) continue;
|
||||||
|
|
||||||
|
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(?:(?:-(?:dev|alpha|beta|rc))+)?</version>|', $xmlContent, $xm)) {
|
||||||
|
$relPath = str_replace($root . '/', '', $file);
|
||||||
|
$relPath = str_replace($root . '\\', '', $relPath);
|
||||||
|
$versions[$relPath] = $xm[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($versions)) {
|
||||||
|
fwrite(STDERR, "No version sources found\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Compare versions ─────────────────────────────────────────────────────────
|
||||||
|
$uniqueVersions = array_unique(array_values($versions));
|
||||||
|
$highestVersion = '00.00.00';
|
||||||
|
foreach ($versions as $v) {
|
||||||
|
if (version_compare($v, $highestVersion, '>')) {
|
||||||
|
$highestVersion = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== Version Consistency Check ===\n";
|
||||||
|
foreach ($versions as $source => $ver) {
|
||||||
|
$status = ($ver === $highestVersion) ? 'OK' : 'MISMATCH';
|
||||||
|
if ($status === 'MISMATCH') $errors++;
|
||||||
|
echo sprintf(" %-50s %s %s\n", $source, $ver, $status === 'OK' ? '' : "** MISMATCH (expected {$highestVersion})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($uniqueVersions) === 1) {
|
||||||
|
echo "\nAll {$ver} — consistent.\n";
|
||||||
|
} else {
|
||||||
|
echo "\n** {$errors} mismatch(es) found. Highest version: {$highestVersion}\n";
|
||||||
|
|
||||||
|
if ($fix) {
|
||||||
|
echo "\n=== Fixing mismatches to {$highestVersion} ===\n";
|
||||||
|
|
||||||
|
// Fix README.md
|
||||||
|
if (isset($versions['README.md']) && $versions['README.md'] !== $highestVersion) {
|
||||||
|
$content = file_get_contents($readme);
|
||||||
|
$updated = preg_replace(
|
||||||
|
'/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}/m',
|
||||||
|
'${1}' . $highestVersion,
|
||||||
|
$content,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
if ($updated !== null) {
|
||||||
|
file_put_contents($readme, $updated);
|
||||||
|
}
|
||||||
|
echo " Fixed: README.md -> {$highestVersion}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix XML manifests
|
||||||
|
foreach ($versions as $source => $ver) {
|
||||||
|
if ($source === 'README.md') continue;
|
||||||
|
if ($ver === $highestVersion) continue;
|
||||||
|
|
||||||
|
$file = "{$root}/{$source}";
|
||||||
|
if (!file_exists($file)) continue;
|
||||||
|
|
||||||
|
$content = file_get_contents($file);
|
||||||
|
$updated = preg_replace(
|
||||||
|
'|<version>[^<]*</version>|',
|
||||||
|
"<version>{$highestVersion}</version>",
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
if ($updated !== null) {
|
||||||
|
file_put_contents($file, $updated);
|
||||||
|
}
|
||||||
|
echo " Fixed: {$source} -> {$highestVersion}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Done.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($strict && $errors > 0) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
+83
-13
@@ -9,7 +9,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/version_read.php
|
* PATH: /cli/version_read.php
|
||||||
* BRIEF: Read version from README.md or manifest XML — outputs the higher of the two
|
* BRIEF: Read version — manifest.xml is canonical, falls back to README.md and Joomla XML
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,7 +23,26 @@ foreach ($argv as $i => $arg) {
|
|||||||
|
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
// ── Read from README.md ──────────────────────────────────────────────────────
|
// -- 1. Read from .mokogitea/manifest.xml (canonical source) --
|
||||||
|
$mokoVersion = null;
|
||||||
|
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
if (file_exists($mokoManifest)) {
|
||||||
|
$xml = @simplexml_load_file($mokoManifest);
|
||||||
|
if ($xml !== false) {
|
||||||
|
$v = (string)($xml->identity->version ?? '');
|
||||||
|
if (preg_match('/^\d{2}\.\d{2}\.\d{2}((?:-(?:dev|alpha|beta|rc))+)?$/', $v)) {
|
||||||
|
$mokoVersion = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If manifest.xml has a version, that is authoritative
|
||||||
|
if ($mokoVersion !== null) {
|
||||||
|
echo $mokoVersion . "\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 2. Fallback: README.md --
|
||||||
$readmeVersion = null;
|
$readmeVersion = null;
|
||||||
$readme = "{$root}/README.md";
|
$readme = "{$root}/README.md";
|
||||||
if (file_exists($readme)) {
|
if (file_exists($readme)) {
|
||||||
@@ -33,7 +52,7 @@ if (file_exists($readme)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Read from Joomla manifest XML ────────────────────────────────────────────
|
// -- 3. Fallback: Joomla manifest XML --
|
||||||
$manifestVersion = null;
|
$manifestVersion = null;
|
||||||
$manifestFiles = array_merge(
|
$manifestFiles = array_merge(
|
||||||
glob("{$root}/src/pkg_*.xml") ?: [],
|
glob("{$root}/src/pkg_*.xml") ?: [],
|
||||||
@@ -47,28 +66,79 @@ foreach ($manifestFiles as $xmlFile) {
|
|||||||
if (strpos($xmlContent, '<extension') === false && strpos($xmlContent, '<version>') === false) {
|
if (strpos($xmlContent, '<extension') === false && strpos($xmlContent, '<version>') === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(?:-[a-z]+)?</version>|', $xmlContent, $xm)) {
|
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?)</version>|', $xmlContent, $xm)) {
|
||||||
$candidate = $xm[1];
|
$candidate = $xm[1];
|
||||||
if ($manifestVersion === null || version_compare($candidate, $manifestVersion, '>')) {
|
$candidateBase = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $candidate);
|
||||||
|
$currentBase = $manifestVersion ? preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $manifestVersion) : null;
|
||||||
|
if ($currentBase === null || version_compare($candidateBase, $currentBase, '>')) {
|
||||||
$manifestVersion = $candidate;
|
$manifestVersion = $candidate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Output the higher version ────────────────────────────────────────────────
|
// -- 4. Fallback: package.json (Node.js / MCP) --
|
||||||
|
$packageJsonVersion = null;
|
||||||
|
$packageJsonFile = "{$root}/package.json";
|
||||||
|
if (file_exists($packageJsonFile)) {
|
||||||
|
$pkgData = json_decode(file_get_contents($packageJsonFile), true);
|
||||||
|
if (isset($pkgData['version']) && preg_match('/^\d{2}\.\d{2}\.\d{2}$/', $pkgData['version'])) {
|
||||||
|
$packageJsonVersion = $pkgData['version'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 5. Fallback: pyproject.toml (Python) --
|
||||||
|
$pyprojectVersion = null;
|
||||||
|
$pyprojectFile = "{$root}/pyproject.toml";
|
||||||
|
if (file_exists($pyprojectFile)) {
|
||||||
|
$pyContent = file_get_contents($pyprojectFile);
|
||||||
|
if (preg_match('/^version\s*=\s*"(\d{2}\.\d{2}\.\d{2})"/m', $pyContent, $pm)) {
|
||||||
|
$pyprojectVersion = $pm[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Output the higher version --
|
||||||
|
$candidates = array_filter([
|
||||||
|
$readmeVersion,
|
||||||
|
$manifestVersion,
|
||||||
|
$packageJsonVersion,
|
||||||
|
$pyprojectVersion,
|
||||||
|
]);
|
||||||
|
|
||||||
$version = null;
|
$version = null;
|
||||||
if ($readmeVersion !== null && $manifestVersion !== null) {
|
foreach ($candidates as $candidate) {
|
||||||
$version = version_compare($manifestVersion, $readmeVersion, '>') ? $manifestVersion : $readmeVersion;
|
if ($version === null || version_compare($candidate, $version, '>')) {
|
||||||
} elseif ($manifestVersion !== null) {
|
$version = $candidate;
|
||||||
$version = $manifestVersion;
|
}
|
||||||
} elseif ($readmeVersion !== null) {
|
|
||||||
$version = $readmeVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($version === null) {
|
if ($version === null) {
|
||||||
fwrite(STDERR, "No version found in README.md or manifest XML\n");
|
fwrite(STDERR, "No version found in manifest.xml, README.md, Joomla XML, package.json, or pyproject.toml\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- Backfill: if manifest.xml exists but lacks <version>, insert it --
|
||||||
|
if (file_exists($mokoManifest)) {
|
||||||
|
$content = file_get_contents($mokoManifest);
|
||||||
|
if (!preg_match('|<version>\d{2}\.\d{2}\.\d{2}((?:-(?:dev|alpha|beta|rc))+)?</version>|', $content)) {
|
||||||
|
if (strpos($content, '<license') !== false) {
|
||||||
|
$content = preg_replace(
|
||||||
|
'|(\s*<license)|',
|
||||||
|
"\n <version>{$version}</version>\$1",
|
||||||
|
$content,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
} elseif (strpos($content, '</identity>') !== false) {
|
||||||
|
$content = preg_replace(
|
||||||
|
'|(</identity>)|',
|
||||||
|
" <version>{$version}</version>\n \$1",
|
||||||
|
$content,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
file_put_contents($mokoManifest, $content);
|
||||||
|
fwrite(STDERR, "Backfilled manifest.xml with version {$version}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
echo $version . "\n";
|
echo $version . "\n";
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|||||||
@@ -0,0 +1,319 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/version_reset_dev.php
|
||||||
|
* BRIEF: Reset platform version to 'development' on a branch via Gitea API
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php version_reset_dev.php --token TOKEN --api-base URL
|
||||||
|
* php version_reset_dev.php --token TOKEN --api-base URL --branch dev
|
||||||
|
* php version_reset_dev.php --token TOKEN --api-base URL --platform dolibarr
|
||||||
|
* php version_reset_dev.php --token TOKEN --api-base URL --path /repo/root
|
||||||
|
*
|
||||||
|
* This replaces the inline curl+python3+sed block previously used in
|
||||||
|
* auto-release.yml to reset Dolibarr's $this->version on the dev branch
|
||||||
|
* after a stable release.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// ── Argument parsing ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$token = null;
|
||||||
|
$apiBase = null;
|
||||||
|
$branch = 'dev';
|
||||||
|
$platform = null;
|
||||||
|
$path = null;
|
||||||
|
|
||||||
|
foreach ($argv as $i => $arg) {
|
||||||
|
if ($arg === '--token' && isset($argv[$i + 1])) {
|
||||||
|
$token = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--api-base' && isset($argv[$i + 1])) {
|
||||||
|
$apiBase = rtrim($argv[$i + 1], '/');
|
||||||
|
}
|
||||||
|
if ($arg === '--branch' && isset($argv[$i + 1])) {
|
||||||
|
$branch = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--platform' && isset($argv[$i + 1])) {
|
||||||
|
$platform = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--path' && isset($argv[$i + 1])) {
|
||||||
|
$path = $argv[$i + 1];
|
||||||
|
}
|
||||||
|
if ($arg === '--help' || $arg === '-h') {
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow token from environment
|
||||||
|
if ($token === null) {
|
||||||
|
$envToken = getenv('MOKOGITEA_TOKEN');
|
||||||
|
if ($envToken !== false && $envToken !== '') {
|
||||||
|
$token = $envToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($token === null) {
|
||||||
|
$envToken = getenv('GITEA_TOKEN');
|
||||||
|
if ($envToken !== false && $envToken !== '') {
|
||||||
|
$token = $envToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token === null || $apiBase === null) {
|
||||||
|
fwrite(STDERR, "Error: --token and --api-base are required.\n\n");
|
||||||
|
printUsage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Platform detection ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ($platform === null && $path !== null) {
|
||||||
|
$platform = detectPlatform($path);
|
||||||
|
if ($platform !== null) {
|
||||||
|
echo "Detected platform: {$platform}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($platform === null) {
|
||||||
|
fwrite(STDERR, "Error: could not determine platform. Use --platform or --path.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dispatch by platform ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$changed = 0;
|
||||||
|
|
||||||
|
if (in_array($platform, ['dolibarr', 'crm-module'], true)) {
|
||||||
|
$changed = resetDolibarrVersion($apiBase, $token, $branch);
|
||||||
|
} elseif (in_array($platform, ['joomla', 'waas-component'], true)) {
|
||||||
|
echo "Joomla version reset is not yet implemented — skipping.\n";
|
||||||
|
} else {
|
||||||
|
echo "Platform '{$platform}' has no version-reset logic — skipping.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Reset {$changed} file(s) to 'development' on branch '{$branch}'.\n";
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
// Helper functions
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print usage information to stdout.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function printUsage(): void
|
||||||
|
{
|
||||||
|
echo <<<'USAGE'
|
||||||
|
Reset platform version to 'development' on a branch via Gitea API.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
php version_reset_dev.php --token TOKEN --api-base URL [options]
|
||||||
|
|
||||||
|
Required:
|
||||||
|
--token TOKEN Gitea API token (also reads MOKOGITEA_TOKEN / GITEA_TOKEN env)
|
||||||
|
--api-base URL Gitea API base URL for the repo
|
||||||
|
e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--branch BRANCH Target branch (default: dev)
|
||||||
|
--platform TYPE Platform type: dolibarr, crm-module, joomla, waas-component
|
||||||
|
--path DIR Repo root for auto-detecting platform from manifest.xml
|
||||||
|
--help Show this help
|
||||||
|
|
||||||
|
USAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the platform type from a repo's .mokogitea/manifest.xml file.
|
||||||
|
*
|
||||||
|
* @param string $repoPath Path to the repository root
|
||||||
|
* @return string|null The detected platform, or null if detection fails
|
||||||
|
*/
|
||||||
|
function detectPlatform(string $repoPath): ?string
|
||||||
|
{
|
||||||
|
$root = realpath($repoPath) ?: $repoPath;
|
||||||
|
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
||||||
|
|
||||||
|
if (!file_exists($manifestXml)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$xml = @simplexml_load_file($manifestXml);
|
||||||
|
if ($xml === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($xml->governance->platform)) {
|
||||||
|
$platform = (string) $xml->governance->platform;
|
||||||
|
if ($platform !== '') {
|
||||||
|
return $platform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a Gitea API call and return the decoded JSON response.
|
||||||
|
*
|
||||||
|
* @param string $url Full API URL
|
||||||
|
* @param string $token Gitea API token
|
||||||
|
* @param string $method HTTP method (GET, PUT, POST, DELETE)
|
||||||
|
* @param string|null $body JSON request body, or null for bodiless requests
|
||||||
|
* @return array<string, mixed>|null Decoded JSON response, or null on failure
|
||||||
|
*/
|
||||||
|
function giteaApiCall(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
if ($ch === false) {
|
||||||
|
fwrite(STDERR, "Error: curl_init() failed for {$url}\n");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
"Authorization: token {$token}",
|
||||||
|
'Accept: application/json',
|
||||||
|
];
|
||||||
|
if ($body !== null) {
|
||||||
|
$headers[] = 'Content-Type: application/json';
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($body !== null) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300 || !is_string($response) || $response === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset Dolibarr module version to 'development' on the target branch.
|
||||||
|
*
|
||||||
|
* Searches the repository tree for mod*.class.php files that contain
|
||||||
|
* `extends DolibarrModules`, then replaces `$this->version = '...'`
|
||||||
|
* with `$this->version = 'development'` via the Gitea file contents API.
|
||||||
|
*
|
||||||
|
* @param string $apiBase Gitea API base URL for the repo
|
||||||
|
* @param string $token Gitea API token
|
||||||
|
* @param string $branch Target branch name
|
||||||
|
* @return int Number of files modified
|
||||||
|
*/
|
||||||
|
function resetDolibarrVersion(string $apiBase, string $token, string $branch): int
|
||||||
|
{
|
||||||
|
// Search the repo tree for mod*.class.php files
|
||||||
|
$treeUrl = "{$apiBase}/git/trees/{$branch}?recursive=true";
|
||||||
|
$tree = giteaApiCall($treeUrl, $token);
|
||||||
|
|
||||||
|
if ($tree === null || !isset($tree['tree']) || !is_array($tree['tree'])) {
|
||||||
|
fwrite(STDERR, "Error: could not read repository tree for branch '{$branch}'.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find candidate files: mod*.class.php anywhere in the tree
|
||||||
|
$candidates = [];
|
||||||
|
foreach ($tree['tree'] as $entry) {
|
||||||
|
if (!isset($entry['path']) || !is_string($entry['path'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$basename = basename($entry['path']);
|
||||||
|
if (preg_match('/^mod[A-Za-z0-9_]+\.class\.php$/', $basename)) {
|
||||||
|
$candidates[] = $entry['path'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($candidates)) {
|
||||||
|
echo "No mod*.class.php files found on branch '{$branch}'.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$changed = 0;
|
||||||
|
|
||||||
|
foreach ($candidates as $filePath) {
|
||||||
|
// GET file contents via API
|
||||||
|
$encodedPath = implode('/', array_map('rawurlencode', explode('/', $filePath)));
|
||||||
|
$fileUrl = "{$apiBase}/contents/{$encodedPath}?ref={$branch}";
|
||||||
|
$fileData = giteaApiCall($fileUrl, $token);
|
||||||
|
|
||||||
|
if ($fileData === null || !isset($fileData['content'])) {
|
||||||
|
echo "Skipping {$filePath}: could not fetch contents.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode base64 content
|
||||||
|
$rawContent = is_string($fileData['content']) ? $fileData['content'] : '';
|
||||||
|
$content = base64_decode($rawContent, true);
|
||||||
|
if ($content === false) {
|
||||||
|
echo "Skipping {$filePath}: could not decode content.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify this file extends DolibarrModules
|
||||||
|
if (!str_contains($content, 'extends DolibarrModules')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace $this->version = '...' with $this->version = 'development'
|
||||||
|
$updated = preg_replace(
|
||||||
|
'/(\$this->version\s*=\s*)[\'"][^\'"]*[\'"]/',
|
||||||
|
"\${1}'development'",
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($updated === null || $updated === $content) {
|
||||||
|
echo "Skipping {$filePath}: no version change needed.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT updated content back via API
|
||||||
|
$sha = $fileData['sha'] ?? '';
|
||||||
|
$putBody = json_encode([
|
||||||
|
'content' => base64_encode($updated),
|
||||||
|
'message' => 'chore(version): reset dev version [skip ci]',
|
||||||
|
'branch' => $branch,
|
||||||
|
'sha' => $sha,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$putUrl = "{$apiBase}/contents/{$encodedPath}";
|
||||||
|
$result = giteaApiCall($putUrl, $token, 'PUT', $putBody);
|
||||||
|
|
||||||
|
if ($result !== null) {
|
||||||
|
echo "Reset: {$filePath} -> \$this->version = 'development'\n";
|
||||||
|
$changed++;
|
||||||
|
} else {
|
||||||
|
fwrite(STDERR, "Error: failed to update {$filePath} on branch '{$branch}'.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $changed;
|
||||||
|
}
|
||||||
@@ -46,6 +46,9 @@ if ($version === null) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip any existing suffix(es) before applying the correct one
|
||||||
|
$version = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $version);
|
||||||
|
|
||||||
// Append stability suffix for non-stable releases
|
// Append stability suffix for non-stable releases
|
||||||
$stabilitySuffixMap = [
|
$stabilitySuffixMap = [
|
||||||
'stable' => '',
|
'stable' => '',
|
||||||
@@ -149,7 +152,7 @@ if (in_array($platform, ['waas-component', 'joomla'], true)) {
|
|||||||
"<version>{$version}</version>",
|
"<version>{$version}</version>",
|
||||||
$content
|
$content
|
||||||
);
|
);
|
||||||
if ($updated !== $content) {
|
if ($updated !== null && $updated !== $content) {
|
||||||
file_put_contents($file, $updated);
|
file_put_contents($file, $updated);
|
||||||
$relPath = str_replace($root . '/', '', $file);
|
$relPath = str_replace($root . '/', '', $file);
|
||||||
echo "Joomla: {$relPath} → {$version}\n";
|
echo "Joomla: {$relPath} → {$version}\n";
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: moko-platform.CLI
|
||||||
|
* INGROUP: moko-platform
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
* PATH: /cli/wiki_sync.php
|
||||||
|
* VERSION: 01.00.00
|
||||||
|
* BRIEF: Sync select wiki pages from moko-platform to all template repos
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
final class WikiSync
|
||||||
|
{
|
||||||
|
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
||||||
|
private string $token = '';
|
||||||
|
private string $org = 'MokoConsulting';
|
||||||
|
private string $sourceRepo = 'moko-platform';
|
||||||
|
private array $targetRepos = [];
|
||||||
|
private array $pages = [];
|
||||||
|
private bool $dryRun = false;
|
||||||
|
private bool $allTemplates = false;
|
||||||
|
|
||||||
|
private int $synced = 0;
|
||||||
|
private int $created = 0;
|
||||||
|
private int $skipped = 0;
|
||||||
|
private int $errors = 0;
|
||||||
|
|
||||||
|
public function run(): int
|
||||||
|
{
|
||||||
|
$this->parseArgs();
|
||||||
|
|
||||||
|
if ($this->token === '') {
|
||||||
|
$this->log('ERROR: --token is required.');
|
||||||
|
$this->printUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->pages) && !$this->allTemplates) {
|
||||||
|
$this->log('ERROR: --page or --all-standards is required.');
|
||||||
|
$this->printUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover template repos if --all-templates
|
||||||
|
if ($this->allTemplates || empty($this->targetRepos)) {
|
||||||
|
$this->targetRepos = $this->discoverTemplateRepos();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->targetRepos)) {
|
||||||
|
$this->log('No target repos found.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If --all-standards, get all pages that start with uppercase
|
||||||
|
if (empty($this->pages)) {
|
||||||
|
$this->pages = $this->getStandardsPages();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log("Syncing " . count($this->pages) . " page(s) to " . count($this->targetRepos) . " repo(s)");
|
||||||
|
if ($this->dryRun) {
|
||||||
|
$this->log("[DRY RUN] No changes will be made.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->pages as $pageName) {
|
||||||
|
$this->log("\n--- Page: {$pageName} ---");
|
||||||
|
$sourceContent = $this->getWikiPage($this->sourceRepo, $pageName);
|
||||||
|
if ($sourceContent === null) {
|
||||||
|
$this->log(" WARNING: page not found in {$this->sourceRepo}");
|
||||||
|
$this->errors++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->targetRepos as $repo) {
|
||||||
|
$existing = $this->getWikiPage($repo, $pageName);
|
||||||
|
if ($existing !== null && $existing === $sourceContent) {
|
||||||
|
$this->log(" {$repo}: IDENTICAL (skipped)");
|
||||||
|
$this->skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->dryRun) {
|
||||||
|
$action = $existing !== null ? 'WOULD UPDATE' : 'WOULD CREATE';
|
||||||
|
$this->log(" {$repo}: {$action}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing !== null) {
|
||||||
|
$ok = $this->updateWikiPage($repo, $pageName, $sourceContent);
|
||||||
|
$this->log(" {$repo}: " . ($ok ? 'UPDATED' : 'ERROR'));
|
||||||
|
$ok ? $this->synced++ : $this->errors++;
|
||||||
|
} else {
|
||||||
|
$ok = $this->createWikiPage($repo, $pageName, $sourceContent);
|
||||||
|
$this->log(" {$repo}: " . ($ok ? 'CREATED' : 'ERROR'));
|
||||||
|
$ok ? $this->created++ : $this->errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log("\nDone: {$this->synced} updated, {$this->created} created, {$this->skipped} skipped, {$this->errors} error(s)");
|
||||||
|
return $this->errors > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function discoverTemplateRepos(): array
|
||||||
|
{
|
||||||
|
$repos = $this->apiGet("/orgs/{$this->org}/repos?limit=100");
|
||||||
|
$templates = [];
|
||||||
|
foreach ($repos as $repo) {
|
||||||
|
if (str_starts_with($repo['name'], 'Template-') && !($repo['archived'] ?? false)) {
|
||||||
|
$templates[] = $repo['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($templates);
|
||||||
|
$this->log("Found template repos: " . implode(', ', $templates));
|
||||||
|
return $templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getStandardsPages(): array
|
||||||
|
{
|
||||||
|
$pages = $this->apiGet("/repos/{$this->org}/{$this->sourceRepo}/wiki/pages");
|
||||||
|
$standards = [];
|
||||||
|
foreach ($pages as $page) {
|
||||||
|
$title = $page['title'] ?? '';
|
||||||
|
// Sync pages that are all-caps with underscores (standards pages)
|
||||||
|
if (preg_match('/^[A-Z][A-Z0-9_-]+$/', $title)) {
|
||||||
|
$standards[] = $title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($standards);
|
||||||
|
$this->log("Found " . count($standards) . " standards pages: " . implode(', ', $standards));
|
||||||
|
return $standards;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getWikiPage(string $repo, string $pageName): ?string
|
||||||
|
{
|
||||||
|
$data = $this->apiGet("/repos/{$this->org}/{$repo}/wiki/page/{$pageName}");
|
||||||
|
if ($data === null || !isset($data['content_base64'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return base64_decode($data['content_base64']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createWikiPage(string $repo, string $pageName, string $content): bool
|
||||||
|
{
|
||||||
|
$payload = json_encode([
|
||||||
|
'title' => $pageName,
|
||||||
|
'content_base64' => base64_encode($content),
|
||||||
|
]);
|
||||||
|
return $this->apiPost("/repos/{$this->org}/{$repo}/wiki/new", $payload) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateWikiPage(string $repo, string $pageName, string $content): bool
|
||||||
|
{
|
||||||
|
$payload = json_encode([
|
||||||
|
'title' => $pageName,
|
||||||
|
'content_base64' => base64_encode($content),
|
||||||
|
]);
|
||||||
|
return $this->apiPatch("/repos/{$this->org}/{$repo}/wiki/page/{$pageName}", $payload) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function apiGet(string $endpoint): ?array
|
||||||
|
{
|
||||||
|
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
|
||||||
|
$opts = [
|
||||||
|
'http' => [
|
||||||
|
'method' => 'GET',
|
||||||
|
'header' => "Authorization: token {$this->token}\r\nAccept: application/json\r\n",
|
||||||
|
'ignore_errors' => true,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$ctx = stream_context_create($opts);
|
||||||
|
$result = @file_get_contents($url, false, $ctx);
|
||||||
|
if ($result === false) return null;
|
||||||
|
$data = json_decode($result, true);
|
||||||
|
return is_array($data) ? $data : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function apiPost(string $endpoint, string $payload): ?array
|
||||||
|
{
|
||||||
|
return $this->apiWrite('POST', $endpoint, $payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function apiPatch(string $endpoint, string $payload): ?array
|
||||||
|
{
|
||||||
|
return $this->apiWrite('PATCH', $endpoint, $payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function apiWrite(string $method, string $endpoint, string $payload): ?array
|
||||||
|
{
|
||||||
|
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
|
||||||
|
$opts = [
|
||||||
|
'http' => [
|
||||||
|
'method' => $method,
|
||||||
|
'header' => "Authorization: token {$this->token}\r\nContent-Type: application/json\r\nAccept: application/json\r\n",
|
||||||
|
'content' => $payload,
|
||||||
|
'ignore_errors' => true,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$ctx = stream_context_create($opts);
|
||||||
|
$result = @file_get_contents($url, false, $ctx);
|
||||||
|
if ($result === false) return null;
|
||||||
|
$data = json_decode($result, true);
|
||||||
|
return is_array($data) ? $data : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseArgs(): void
|
||||||
|
{
|
||||||
|
global $argv;
|
||||||
|
$args = $argv;
|
||||||
|
for ($i = 1; $i < count($args); $i++) {
|
||||||
|
switch ($args[$i]) {
|
||||||
|
case '--token':
|
||||||
|
$this->token = $args[++$i] ?? '';
|
||||||
|
break;
|
||||||
|
case '--org':
|
||||||
|
$this->org = $args[++$i] ?? '';
|
||||||
|
break;
|
||||||
|
case '--source':
|
||||||
|
$this->sourceRepo = $args[++$i] ?? '';
|
||||||
|
break;
|
||||||
|
case '--target':
|
||||||
|
$this->targetRepos[] = $args[++$i] ?? '';
|
||||||
|
break;
|
||||||
|
case '--page':
|
||||||
|
$this->pages[] = $args[++$i] ?? '';
|
||||||
|
break;
|
||||||
|
case '--all-standards':
|
||||||
|
$this->pages = []; // will be populated from source wiki
|
||||||
|
$this->allTemplates = true;
|
||||||
|
break;
|
||||||
|
case '--all-templates':
|
||||||
|
$this->allTemplates = true;
|
||||||
|
break;
|
||||||
|
case '--dry-run':
|
||||||
|
$this->dryRun = true;
|
||||||
|
break;
|
||||||
|
case '--help':
|
||||||
|
case '-h':
|
||||||
|
$this->printUsage();
|
||||||
|
exit(0);
|
||||||
|
default:
|
||||||
|
$this->log("WARNING: Unknown argument: {$args[$i]}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printUsage(): void
|
||||||
|
{
|
||||||
|
$this->log('Usage: wiki_sync.php --token <token> [options]');
|
||||||
|
$this->log('');
|
||||||
|
$this->log('Sync wiki pages from moko-platform to template repos.');
|
||||||
|
$this->log('');
|
||||||
|
$this->log('Options:');
|
||||||
|
$this->log(' --token <token> Gitea API token (required)');
|
||||||
|
$this->log(' --org <org> Organization (default: MokoConsulting)');
|
||||||
|
$this->log(' --source <repo> Source repo (default: moko-platform)');
|
||||||
|
$this->log(' --target <repo> Target repo (can repeat; default: all Template-* repos)');
|
||||||
|
$this->log(' --page <name> Page to sync (can repeat)');
|
||||||
|
$this->log(' --all-standards Sync all UPPERCASE standards pages');
|
||||||
|
$this->log(' --all-templates Target all Template-* repos');
|
||||||
|
$this->log(' --dry-run Show what would be done');
|
||||||
|
$this->log(' --help, -h Show this help');
|
||||||
|
$this->log('');
|
||||||
|
$this->log('Examples:');
|
||||||
|
$this->log(' php wiki_sync.php --token xxx --page MANIFEST_STANDARD --all-templates');
|
||||||
|
$this->log(' php wiki_sync.php --token xxx --all-standards --all-templates --dry-run');
|
||||||
|
$this->log(' php wiki_sync.php --token xxx --page WORKFLOW_STANDARDS --target Template-Joomla');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function log(string $msg): void
|
||||||
|
{
|
||||||
|
fwrite(STDERR, $msg . "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(new WikiSync())->run();
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "mokoconsulting-tech/enterprise",
|
"name": "mokoconsulting-tech/enterprise",
|
||||||
"description": "MokoStandards Enterprise API \u2014 PHP implementation",
|
"description": "MokoStandards Enterprise API \u2014 PHP implementation",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"version": "07.00.00",
|
"version": "09.01.00",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
/**
|
|
||||||
* Client Repository Structure Definition
|
|
||||||
* Standard repository structure for managed Joomla client sites (WaaS)
|
|
||||||
*
|
|
||||||
* This is NOT a Joomla extension — it's a full managed client site with
|
|
||||||
* deployment configs, monitoring, SFTP settings, and sync workflows.
|
|
||||||
* The src/ directory mirrors the Joomla site's public_html.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Client Site"
|
|
||||||
description = "Managed Joomla client site — full site structure, not an extension"
|
|
||||||
repository_type = "client"
|
|
||||||
platform = "client"
|
|
||||||
last_updated = "2026-05-09T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "01.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
detection_hints = [
|
|
||||||
"scripts/sftp-config/",
|
|
||||||
"scripts/sync-dev-to-live.sh",
|
|
||||||
"monitoring/grafana/",
|
|
||||||
"src/administrator/",
|
|
||||||
"src/components/",
|
|
||||||
"src/plugins/",
|
|
||||||
"src/templates/",
|
|
||||||
"src/media/templates/site/mokoonyx/"
|
|
||||||
]
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Client site overview and deployment info"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Release history"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "GPL-3.0-or-later license file"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/LICENSE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
extension = ""
|
|
||||||
description = "Build and deployment targets (includes minify)"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "PHP dependencies"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = ""
|
|
||||||
description = "Git ignore rules (must include *.min.css, *.min.js, TODO.md)"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Joomla site public_html mirror — deployed via SFTP"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains the full Joomla site directory structure"
|
|
||||||
subdirectories = [
|
|
||||||
{ name = "administrator", path = "src/administrator", description = "Joomla admin", required = true },
|
|
||||||
{ name = "components", path = "src/components", description = "Frontend components", required = true },
|
|
||||||
{ name = "plugins", path = "src/plugins", description = "Plugins", required = true },
|
|
||||||
{ name = "modules", path = "src/modules", description = "Modules", required = true },
|
|
||||||
{ name = "templates", path = "src/templates", description = "Templates", required = true },
|
|
||||||
{ name = "media", path = "src/media", description = "Media assets", required = true },
|
|
||||||
{ name = "images", path = "src/images", description = "Site images", required = false },
|
|
||||||
{ name = "language", path = "src/language", description = "Language files", required = false },
|
|
||||||
{ name = "libraries", path = "src/libraries", description = "Libraries", required = false },
|
|
||||||
{ name = "layouts", path = "src/layouts", description = "Layouts", required = false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Deployment, sync, and monitoring scripts"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains SFTP configs, sync scripts, and monitoring"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "sftp-config"
|
|
||||||
path = "scripts/sftp-config"
|
|
||||||
description = "SFTP connection configs (dev + live)"
|
|
||||||
required = true
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "sftp-config.dev.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Dev server SFTP connection"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sftp-config.rs.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Live/release server SFTP connection"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "monitoring"
|
|
||||||
path = "monitoring"
|
|
||||||
description = "Grafana dashboard templates"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains Panopticon-style Grafana dashboard JSON"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "grafana"
|
|
||||||
path = "monitoring/grafana"
|
|
||||||
description = "Grafana dashboard JSON templates"
|
|
||||||
required = true
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "client-joomla-dashboard.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Panopticon-style Grafana dashboard template"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/monitoring/client-joomla-dashboard.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitea"
|
|
||||||
path = ".gitea"
|
|
||||||
description = "Gitea configuration"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains Gitea Actions workflows"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".gitea/workflows"
|
|
||||||
description = "Gitea Actions CI/CD workflows"
|
|
||||||
required = true
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-release on merge to main"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deploy src/ to servers via SFTP"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "add-endpoint.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Add monitoring endpoint to sites.json"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output "client_structure" {
|
|
||||||
description = "Client site repository structure definition"
|
|
||||||
value = local.repository_structure
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
{
|
|
||||||
"schemaVersion": "1.0",
|
|
||||||
"metadata": {
|
|
||||||
"name": "Default Repository Structure",
|
|
||||||
"description": "Default repository structure applicable to all repository types with minimal requirements",
|
|
||||||
"repositoryType": "library",
|
|
||||||
"platform": "multi-platform",
|
|
||||||
"lastUpdated": "2026-01-16T00:00:00Z",
|
|
||||||
"maintainer": "Moko Consulting"
|
|
||||||
},
|
|
||||||
"structure": {
|
|
||||||
"rootFiles": [
|
|
||||||
{
|
|
||||||
"name": "README.md",
|
|
||||||
"extension": "md",
|
|
||||||
"description": "Project overview and documentation",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"audience": "general",
|
|
||||||
"template": "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LICENSE",
|
|
||||||
"extension": "",
|
|
||||||
"description": "License file (GPL-3.0-or-later)",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"audience": "general",
|
|
||||||
"template": "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CHANGELOG.md",
|
|
||||||
"extension": "md",
|
|
||||||
"description": "Version history and changes",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"audience": "general",
|
|
||||||
"template": "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CONTRIBUTING.md",
|
|
||||||
"extension": "md",
|
|
||||||
"description": "Contribution guidelines",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"audience": "contributor",
|
|
||||||
"template": "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SECURITY.md",
|
|
||||||
"extension": "md",
|
|
||||||
"description": "Security policy and vulnerability reporting",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"audience": "general",
|
|
||||||
"template": "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CODE_OF_CONDUCT.md",
|
|
||||||
"extension": "md",
|
|
||||||
"description": "Community code of conduct",
|
|
||||||
"requirementStatus": "suggested",
|
|
||||||
"audience": "contributor",
|
|
||||||
"template": "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": ".gitignore",
|
|
||||||
"extension": "gitignore",
|
|
||||||
"description": "Git ignore patterns",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"alwaysOverwrite": false,
|
|
||||||
"audience": "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": ".gitattributes",
|
|
||||||
"extension": "gitattributes",
|
|
||||||
"description": "Git attributes configuration",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"audience": "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": ".editorconfig",
|
|
||||||
"extension": "editorconfig",
|
|
||||||
"description": "Editor configuration for consistent coding style",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"alwaysOverwrite": false,
|
|
||||||
"audience": "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Makefile",
|
|
||||||
"description": "Build automation",
|
|
||||||
"requirementStatus": "suggested",
|
|
||||||
"audience": "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "renovate.json",
|
|
||||||
"extension": "json",
|
|
||||||
"description": "Renovate dependency management configuration",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"alwaysOverwrite": false,
|
|
||||||
"audience": "developer",
|
|
||||||
"template": "templates/configs/renovate.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directories": [
|
|
||||||
{
|
|
||||||
"name": "docs",
|
|
||||||
"path": "docs",
|
|
||||||
"description": "Documentation directory",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"purpose": "Contains comprehensive project documentation",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"name": "index.md",
|
|
||||||
"extension": "md",
|
|
||||||
"description": "Documentation index",
|
|
||||||
"requirementStatus": "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "scripts",
|
|
||||||
"path": "scripts",
|
|
||||||
"description": "Build and automation scripts",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"purpose": "Contains scripts for building, testing, and deploying"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "src",
|
|
||||||
"path": "src",
|
|
||||||
"description": "Source code directory",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"purpose": "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tests",
|
|
||||||
"path": "tests",
|
|
||||||
"description": "Test files",
|
|
||||||
"requirementStatus": "suggested",
|
|
||||||
"purpose": "Contains unit tests, integration tests, and test fixtures",
|
|
||||||
"subdirectories": [
|
|
||||||
{
|
|
||||||
"name": "unit",
|
|
||||||
"path": "tests/unit",
|
|
||||||
"description": "Unit tests",
|
|
||||||
"requirementStatus": "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "integration",
|
|
||||||
"path": "tests/integration",
|
|
||||||
"description": "Integration tests",
|
|
||||||
"requirementStatus": "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": ".github",
|
|
||||||
"path": ".github",
|
|
||||||
"description": "Gitea/GitHub Actions configuration (Gitea reads .github/workflows natively)",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"purpose": "Contains CI/CD workflows and repository configuration. Gitea is the primary platform; GitHub is backup only.",
|
|
||||||
"subdirectories": [
|
|
||||||
{
|
|
||||||
"name": "workflows",
|
|
||||||
"path": ".github/workflows",
|
|
||||||
"description": "CI/CD workflows (Gitea-primary, GitHub-compatible)",
|
|
||||||
"requirementStatus": "required",
|
|
||||||
"requiredFiles": [
|
|
||||||
"auto-assign.yml",
|
|
||||||
"auto-dev-issue.yml",
|
|
||||||
"auto-release.yml",
|
|
||||||
"branch-freeze.yml",
|
|
||||||
"changelog-validation.yml",
|
|
||||||
"repository-cleanup.yml",
|
|
||||||
"sync-version-on-merge.yml",
|
|
||||||
"cascade-dev.yml",
|
|
||||||
"gitleaks.yml"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "node_modules",
|
|
||||||
"path": "node_modules",
|
|
||||||
"description": "Node.js dependencies (generated)",
|
|
||||||
"requirementStatus": "not-allowed",
|
|
||||||
"purpose": "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vendor",
|
|
||||||
"path": "vendor",
|
|
||||||
"description": "PHP dependencies (generated)",
|
|
||||||
"requirementStatus": "not-allowed",
|
|
||||||
"purpose": "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "build",
|
|
||||||
"path": "build",
|
|
||||||
"description": "Build artifacts (generated)",
|
|
||||||
"requirementStatus": "not-allowed",
|
|
||||||
"purpose": "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dist",
|
|
||||||
"path": "dist",
|
|
||||||
"description": "Distribution files (generated)",
|
|
||||||
"requirementStatus": "not-allowed",
|
|
||||||
"purpose": "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,686 +0,0 @@
|
|||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "renovate.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Renovate dependency management configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/renovate.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "cascade-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Forward-merge main to all open branches (dev, rc/*, beta/*, alpha/*) on push to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "workflows/cascade-dev.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "gitleaks.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Secret scanning — detect leaked credentials, API keys, and tokens using Gitleaks"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "workflows/gitleaks.yml"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 0
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
block_on_rejected_reviews = true
|
|
||||||
restrict_pushes = true
|
|
||||||
push_whitelist = ["jmiller"]
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
enforce_admins = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "dev"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "rc/*"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "beta/*"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "alpha/*"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
/**
|
|
||||||
* .github-private Repository Structure Definition
|
|
||||||
* Org-level private repository containing universal GitHub Actions workflows,
|
|
||||||
* helper scripts, and default issue templates for all governed repositories.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*
|
|
||||||
* NOTES
|
|
||||||
* ─────
|
|
||||||
* • GitHub reads ISSUE_TEMPLATE/ from this repo as org-wide defaults for any
|
|
||||||
* governed repo that does not supply its own templates.
|
|
||||||
* • Workflows in .github/workflows/ support both standalone execution and
|
|
||||||
* workflow_call so governed repos can invoke them as reusable workflows via
|
|
||||||
* `uses: mokoconsulting-tech/.github-private/.github/workflows/<name>.yml@main`.
|
|
||||||
* • This repo is EXCLUDED from bulk-repo-sync — it manages its own content
|
|
||||||
* independently as GitHub's org-level defaults repo.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
github_private_repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = ".github-private"
|
|
||||||
description = "Private GitHub org defaults — universal workflows, issue templates, and helper scripts"
|
|
||||||
repository_type = "github-private"
|
|
||||||
platform = "github-private"
|
|
||||||
last_updated = "2026-03-12T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
visibility = "private"
|
|
||||||
sync_priority = -1
|
|
||||||
exclude_from_sync = true
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Repository overview — purpose, contents, and how governed repos use this repo"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
required = true
|
|
||||||
audience = "general"
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
license_type = "GPL-3.0-or-later"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
required = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and private vulnerability reporting"
|
|
||||||
required = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "contributor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
required = true
|
|
||||||
audience = "contributor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Governance policy and decision-making process"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "general"
|
|
||||||
template = "templates/docs/required/GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
required = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitea/.mokostandards"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
template = "managed-by-sync"
|
|
||||||
source_type = "programmatic"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = "ISSUE_TEMPLATE"
|
|
||||||
description = "Org-default issue templates — applied to all governed repos without their own templates"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "GitHub reads ISSUE_TEMPLATE/ from this repo as org-wide defaults"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Issue template chooser — disables blank issues and lists contact links"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github-private/ISSUE_TEMPLATE/config.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Bug report issue template"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
template = "templates/github-private/ISSUE_TEMPLATE/bug_report.md.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Feature request issue template"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
template = "templates/github-private/ISSUE_TEMPLATE/feature_request.md.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Helper scripts used by universal workflows and available as git hooks"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Reusable Bash utilities for commit-message and PR-title validation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "check-pr-title.sh"
|
|
||||||
extension = "sh"
|
|
||||||
description = "Validates PR title follows conventional-commit format"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github-private/scripts/check-pr-title.sh.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "check-commit-msg.sh"
|
|
||||||
extension = "sh"
|
|
||||||
description = "Validates individual commit messages follow conventional-commit format; usable as a git commit-msg hook"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github-private/scripts/check-commit-msg.sh.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration for .github-private itself"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains CI workflows for this repo and reusable workflows callable org-wide"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "CI + universal reusable workflows; callable via uses: mokoconsulting-tech/.github-private/.github/workflows/<name>.yml@main"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "stale.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Marks stale issues and pull requests; standalone (schedule) and reusable (workflow_call)"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github-private/workflows/stale.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-assign.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-assigns PR author and logs CODEOWNERS status; standalone and reusable"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github-private/workflows/auto-assign.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "pr-labeler.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Labels PRs from branch name and validates PR title format; standalone and reusable"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github-private/workflows/pr-labeler.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "welcome.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Posts welcome message on first-time contributor PRs and issues; standalone and reusable"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github-private/workflows/welcome.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT for automation — required for bulk sync and workflow execution"
|
|
||||||
required = true
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_KEY"
|
|
||||||
description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback"
|
|
||||||
required = false
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_PASSWORD"
|
|
||||||
description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails"
|
|
||||||
required = false
|
|
||||||
scope = "org"
|
|
||||||
note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_HOST"
|
|
||||||
description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)"
|
|
||||||
required = true
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_PATH"
|
|
||||||
description = "Base remote path for SFTP deployment (e.g. /var/www/html)"
|
|
||||||
required = true
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_USERNAME"
|
|
||||||
description = "SFTP username for dev server authentication"
|
|
||||||
required = true
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_PORT"
|
|
||||||
description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22"
|
|
||||||
required = false
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_PATH_SUFFIX"
|
|
||||||
description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /.github-private)"
|
|
||||||
required = false
|
|
||||||
scope = "repo"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
visibility = "private"
|
|
||||||
has_issues = true
|
|
||||||
has_projects = false
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_merge_commit = false
|
|
||||||
allow_rebase_merge = true
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,484 +0,0 @@
|
|||||||
/**
|
|
||||||
* MCP Server Repository Structure Definition
|
|
||||||
* Standard repository structure for Model Context Protocol (MCP) server projects
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "MCP Server"
|
|
||||||
description = "Standard repository structure for Model Context Protocol (MCP) server projects — TypeScript/Node.js MCP servers that expose external APIs as AI assistant tools"
|
|
||||||
repository_type = "mcp-server"
|
|
||||||
platform = "mcp-server"
|
|
||||||
last_updated = "2026-05-07T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "04.06.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview with tool reference table, install, and configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "package.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Node.js project manifest — @mokoconsulting scoped, MCP SDK + Zod dependencies"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tsconfig.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "TypeScript configuration — ES2022 target, Node16 module, strict mode"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "config.example.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Example multi-connection configuration file"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitmessage"
|
|
||||||
extension = "gitmessage"
|
|
||||||
description = "Conventional commit message template"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation — install, build, dev, clean, setup, start targets"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "TypeScript source code"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains MCP server entry point, API client, config loader, and type definitions"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.ts"
|
|
||||||
extension = "ts"
|
|
||||||
description = "MCP server entry point — registers all API tools with McpServer"
|
|
||||||
requirement_status = "required"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "client.ts"
|
|
||||||
extension = "ts"
|
|
||||||
description = "HTTP client wrapper for the target API (GET/POST/PUT/DELETE)"
|
|
||||||
requirement_status = "required"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "config.ts"
|
|
||||||
extension = "ts"
|
|
||||||
description = "Configuration loader — reads ~/.{project}.json with multi-connection support"
|
|
||||||
requirement_status = "required"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "types.ts"
|
|
||||||
extension = "ts"
|
|
||||||
description = "TypeScript interfaces for connection, config, and API response types"
|
|
||||||
requirement_status = "required"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Setup and utility scripts"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains interactive setup wizard and repo-specific helpers"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "setup.mjs"
|
|
||||||
extension = "mjs"
|
|
||||||
description = "Interactive setup wizard — prompts for API connection details and writes config"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitea"
|
|
||||||
path = ".gitea"
|
|
||||||
description = "Gitea-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains Gitea Actions workflows and platform configuration"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = ".mokostandards"
|
|
||||||
description = "MokoStandards platform declaration — must contain 'platform: mcp-server'"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".gitea/workflows"
|
|
||||||
description = "Gitea Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create release on push to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-assign.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-assign issues and PRs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-assign.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/standards-compliance.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "changelog-validation.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CHANGELOG validation on PR"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/changelog-validation.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup of stale branches and workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment to development server"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment to demo server on merge to main"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "copilot-agent.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Copilot agent workflow for automated code review"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/copilot-agent.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "mcp-build-test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MCP server build validation — TypeScript compile, dist verification, tool count"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/mcp/mcp-build-test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "mcp-sdk-check.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Weekly check for MCP SDK and Zod updates — creates issue when new version available"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/mcp/mcp-sdk-check.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "mcp-tool-inventory.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Generate tool inventory report on push to main"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/mcp/mcp-tool-inventory.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Compiled JavaScript output (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level Gitea PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "Gitea Actions workflows"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "20"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "new-tool"
|
|
||||||
color = "5319e7"
|
|
||||||
description = "New MCP tool/endpoint to add"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "api-change"
|
|
||||||
color = "fbca04"
|
|
||||||
description = "Upstream API changed — tool needs update"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dolibarr Platform Structure Definition
|
|
||||||
* Standard repository structure for the full Dolibarr ERP/CRM installation
|
|
||||||
*
|
|
||||||
* This is distinct from dolibarr — it defines the ENTIRE Dolibarr platform
|
|
||||||
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
|
|
||||||
* publish-to-mokodolimods workflow.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Dolibarr Platform"
|
|
||||||
description = "Full Dolibarr ERP/CRM installation — htdocs/ root, not a module"
|
|
||||||
repository_type = "platform"
|
|
||||||
platform = "dolibarr"
|
|
||||||
last_updated = "2026-03-31T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Developer-focused documentation"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
audience = "contributor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "GPL-3.0-or-later license file"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/LICENSE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer package definition"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "phpstan.neon"
|
|
||||||
extension = "neon"
|
|
||||||
description = "PHPStan static analysis configuration"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/phpstan.neon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
extension = ""
|
|
||||||
description = "Build automation targets"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/Makefile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src/.ftpignore"
|
|
||||||
extension = ""
|
|
||||||
description = "Files excluded from SFTP deployment"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/ftp_ignore"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitea/.mokostandards"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
template = "managed-by-sync"
|
|
||||||
source_type = "programmatic"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "renovate.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Renovate dependency management configuration"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/renovate.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "htdocs"
|
|
||||||
path = "htdocs"
|
|
||||||
description = "Dolibarr web root — entire platform"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Developer and technical documentation"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains technical documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "update-server.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Dolibarr update server (update.txt) documentation"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-update-server-dolibarr.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub configuration"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to dev server (htdocs/)"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to demo server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to release staging server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on minor version"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: retired workflows, stale branches"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue on dev/rc branch creation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repo_health.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/dolibarr/repo_health.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "cascade-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Forward-merge main to all open branches (dev, rc/*, beta/*, alpha/*) on push to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "workflows/cascade-dev.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "gitleaks.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Secret scanning — detect leaked credentials, API keys, and tokens using Gitleaks"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "workflows/gitleaks.yml"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dolibarr_issue.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dolibarr_module_id_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output "crm_platform_structure" {
|
|
||||||
description = "Dolibarr Platform repository structure definition"
|
|
||||||
value = local.repository_structure
|
|
||||||
}
|
|
||||||
@@ -1,763 +0,0 @@
|
|||||||
/**
|
|
||||||
* MokoStandards Repository Structure Definition
|
|
||||||
* Repository structure definition for the MokoStandards standards and templates repository
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "MokoStandards Repository"
|
|
||||||
description = "Repository structure definition for MokoStandards - organizational standards, templates, and automation"
|
|
||||||
repository_type = "standards"
|
|
||||||
platform = "standards"
|
|
||||||
last_updated = "2026-03-03T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Repository overview and documentation"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
required = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CITATION.cff"
|
|
||||||
extension = "cff"
|
|
||||||
description = "Citation file format for academic references"
|
|
||||||
required = true
|
|
||||||
audience = "general"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
required = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitmessage"
|
|
||||||
extension = "gitmessage"
|
|
||||||
description = "Git commit message template"
|
|
||||||
required = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".git-blame-ignore-revs"
|
|
||||||
extension = "git-blame-ignore-revs"
|
|
||||||
description = "Git blame ignore revisions"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".mailmap"
|
|
||||||
extension = "mailmap"
|
|
||||||
description = "Git mailmap for contributor attribution"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".eslintrc.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "ESLint configuration for JavaScript"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".prettierrc.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Prettier configuration for code formatting"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".markdownlint.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Markdown linting configuration"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".yamllint"
|
|
||||||
extension = "yamllint"
|
|
||||||
description = "YAML linting configuration"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".pylintrc"
|
|
||||||
extension = "pylintrc"
|
|
||||||
description = "Python linting configuration"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".htmlhintrc"
|
|
||||||
extension = "htmlhintrc"
|
|
||||||
description = "HTML linting configuration"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "PHP dependency management"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitea/.mokostandards"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "managed-by-sync"
|
|
||||||
source_type = "programmatic"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "renovate.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Renovate dependency management configuration"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/renovate.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "api"
|
|
||||||
path = "api"
|
|
||||||
description = "API scripts and automation"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains all operational scripts - validation, automation, build, release, etc."
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "validate"
|
|
||||||
path = "api/validate"
|
|
||||||
description = "Validation scripts"
|
|
||||||
required = true
|
|
||||||
purpose = "Scripts for validating repository structure, health, and compliance"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "automation"
|
|
||||||
path = "api/automation"
|
|
||||||
description = "Automation scripts"
|
|
||||||
required = true
|
|
||||||
purpose = "Scripts for bulk operations and repository synchronization"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "api/build"
|
|
||||||
description = "Build scripts"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Scripts for building and packaging"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release"
|
|
||||||
path = "api/release"
|
|
||||||
description = "Release scripts"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Scripts for release management"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "api/tests"
|
|
||||||
description = "Test scripts"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Test scripts and test data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "maintenance"
|
|
||||||
path = "api/maintenance"
|
|
||||||
description = "Maintenance scripts"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Scripts for repository maintenance tasks"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "definitions"
|
|
||||||
path = "api/definitions"
|
|
||||||
description = "Repository structure definitions"
|
|
||||||
required = true
|
|
||||||
purpose = "HCL/Terraform definition files for different repository types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "lib"
|
|
||||||
path = "api/lib"
|
|
||||||
description = "Shared libraries"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Shared code libraries and utilities"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation"
|
|
||||||
required = true
|
|
||||||
purpose = "Comprehensive documentation for standards, guides, policies, and references"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "guide"
|
|
||||||
path = "docs/guide"
|
|
||||||
description = "User guides"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "reference"
|
|
||||||
path = "docs/reference"
|
|
||||||
description = "Reference documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "policy"
|
|
||||||
path = "docs/policy"
|
|
||||||
description = "Policies and standards"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = "docs/workflows"
|
|
||||||
description = "Workflow documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
path = "docs/security"
|
|
||||||
description = "Security documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "development"
|
|
||||||
path = "docs/development"
|
|
||||||
description = "Development documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "templates"
|
|
||||||
path = "templates"
|
|
||||||
description = "Template files"
|
|
||||||
required = true
|
|
||||||
purpose = "Template files for workflows, configs, documentation, and projects"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = "templates/workflows"
|
|
||||||
description = "GitHub Actions workflow templates"
|
|
||||||
required = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "github"
|
|
||||||
path = "templates/github"
|
|
||||||
description = "GitHub configuration templates"
|
|
||||||
required = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "templates/docs"
|
|
||||||
description = "Documentation templates"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "configs"
|
|
||||||
path = "templates/configs"
|
|
||||||
description = "Configuration file templates"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "licenses"
|
|
||||||
path = "templates/licenses"
|
|
||||||
description = "License templates"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "projects"
|
|
||||||
path = "templates/projects"
|
|
||||||
description = "Project definition templates"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "terraform"
|
|
||||||
path = "templates/terraform"
|
|
||||||
description = "Terraform configuration templates"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "templates/scripts"
|
|
||||||
description = "Script templates"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "logs"
|
|
||||||
path = "logs"
|
|
||||||
description = "Log files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Storage for operation logs, audit trails, and metrics"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "audit"
|
|
||||||
path = "logs/audit"
|
|
||||||
description = "Audit logs"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "automation"
|
|
||||||
path = "logs/automation"
|
|
||||||
description = "Automation logs"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "validation"
|
|
||||||
path = "logs/validation"
|
|
||||||
description = "Validation logs"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
required = true
|
|
||||||
purpose = "GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
required = true
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "One-time cleanup: reset labels, strip issue template headers, delete old branches — self-deletes after run"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards self-compliance validation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall rules setup workflow"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "cascade-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Forward-merge main to all open branches (dev, rc/*, beta/*, alpha/*) on push to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "workflows/cascade-dev.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "gitleaks.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Secret scanning — detect leaked credentials, API keys, and tokens using Gitleaks"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "workflows/gitleaks.yml"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.tf"
|
|
||||||
extension = "tf"
|
|
||||||
description = "Repository override configuration for bulk sync"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "copilot.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "GitHub Copilot configuration — topic list and repo metadata"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "copilot-instructions.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "GitHub Copilot custom instructions for this repository"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CLAUDE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Claude Code context and instructions for this repository"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".checkpoints"
|
|
||||||
path = ".checkpoints"
|
|
||||||
description = "Checkpoint files for long-running operations"
|
|
||||||
requirement_status = "optional"
|
|
||||||
purpose = "Stores checkpoint data for resumable operations"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT for automation — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_KEY"
|
|
||||||
description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback"
|
|
||||||
required = false
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_PASSWORD"
|
|
||||||
description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails"
|
|
||||||
required = false
|
|
||||||
scope = "org"
|
|
||||||
note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "STANDARDS_VERSION"
|
|
||||||
description = "Current MokoStandards version"
|
|
||||||
required = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_HOST"
|
|
||||||
description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)"
|
|
||||||
required = true
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_PATH"
|
|
||||||
description = "Base remote path for SFTP deployment (e.g. /var/www/html)"
|
|
||||||
required = true
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_USERNAME"
|
|
||||||
description = "SFTP username for dev server authentication"
|
|
||||||
required = true
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_PORT"
|
|
||||||
description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22"
|
|
||||||
required = false
|
|
||||||
scope = "org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "DEV_FTP_SUFFIX"
|
|
||||||
description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /mokostandards)"
|
|
||||||
required = false
|
|
||||||
scope = "repo"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 0
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
block_on_rejected_reviews = true
|
|
||||||
restrict_pushes = true
|
|
||||||
push_whitelist = ["jmiller"]
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
enforce_admins = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "dev"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "rc/*"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "beta/*"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "alpha/*"
|
|
||||||
require_pull_request = false
|
|
||||||
required_approvals = 0
|
|
||||||
restrict_pushes = false
|
|
||||||
enable_force_push = true
|
|
||||||
force_push_whitelist = ["jmiller"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_merge_commit = false
|
|
||||||
allow_rebase_merge = true
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{ name = "bulk-sync-success", color = "0e8a16", description = "Bulk sync completed successfully" },
|
|
||||||
{ name = "bulk-sync-failure", color = "d73a4a", description = "Bulk sync failed" },
|
|
||||||
{ name = "standards-update", color = "fbca04", description = "Standards update" },
|
|
||||||
{ name = "template-update", color = "d4c5f9", description = "Template file update" },
|
|
||||||
{ name = "documentation", color = "0075ca", description = "Documentation changes" },
|
|
||||||
{ name = "automation", color = "5319e7", description = "Automation scripts" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
FILE INFORMATION
|
|
||||||
DEFGROUP: MokoStandards.Index
|
|
||||||
INGROUP: MokoStandards.Definitions
|
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
||||||
PATH: /definitions/index.md
|
|
||||||
BRIEF: Definitions directory index
|
|
||||||
-->
|
|
||||||
|
|
||||||
# Docs Index: /api/definitions
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This index provides navigation to documentation within this folder.
|
|
||||||
|
|
||||||
## Documents
|
|
||||||
|
|
||||||
- [README](./README.md)
|
|
||||||
|
|
||||||
## Metadata
|
|
||||||
|
|
||||||
- **Document Type:** index
|
|
||||||
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
|
|
||||||
|
|
||||||
## Revision History
|
|
||||||
|
|
||||||
| Date | Author | Change | Notes |
|
|
||||||
| ---------- | ------------------ | ----------------- | ------------------------------------------ |
|
|
||||||
| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation |
|
|
||||||
@@ -1,682 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/.github-private
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-03-24T19:30:16+00:00
|
|
||||||
* Platform : default-repository
|
|
||||||
* Description: This is the private organization-level configuration repository for mokoconsulting-tech. It provides centralized community health files, templates, and standards that apply across all private repositories in the organization.
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/default-repository.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/.github-private"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "default-repository"
|
|
||||||
description = "This is the private organization-level configuration repository for mokoconsulting-tech. It provides centralized community health files, templates, and standards that apply across all private repositories in the organization."
|
|
||||||
sync_timestamp = "2026-03-24T19:30:16+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/default-repository.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 39
|
|
||||||
created_files = 0
|
|
||||||
updated_files = 28
|
|
||||||
skipped_files = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "updated" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "updated" },
|
|
||||||
{ path = ".github/workflows/ci.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/ci.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "ci.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Continuous integration workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "ci.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "ci.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/ci.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repo-health.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Repository health monitoring"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "repo_health.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "repo-health.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/repo_health.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci", "code-quality"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "master"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,733 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/.github
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T21:05:55+00:00
|
|
||||||
* Platform : default-repository
|
|
||||||
* Description:
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/default-repository.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/.github"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "default-repository"
|
|
||||||
description = ""
|
|
||||||
sync_timestamp = "2026-04-02T21:05:55+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/default-repository.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 42
|
|
||||||
updated_files = 5
|
|
||||||
skipped_files = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "created" },
|
|
||||||
{ path = "CONTRIBUTING.md" action = "created" },
|
|
||||||
{ path = "SECURITY.md" action = "created" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" action = "created" },
|
|
||||||
{ path = "ROADMAP.md" action = "created" },
|
|
||||||
{ path = "Makefile" action = "created" },
|
|
||||||
{ path = "composer.json" action = "created" },
|
|
||||||
{ path = "docs/index.md" action = "created" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "created" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "created" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "created" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "created" },
|
|
||||||
{ path = "composer.json" action = "enterprise dependency added" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci", "code-quality"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "master"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,734 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/Copy-PortablePath
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:20:12+00:00
|
|
||||||
* Platform : default-repository
|
|
||||||
* Description: Copy Portable Path is a lightweight PowerShell utility that adds two context menu items to Windows Explorer — Copy Relative Path and Copy Absolute Path — both using forward slashes for cross-platform compatibility. Supports multiple selections, optional MSYS/WSL /c/... style, and installs per-user without admin rights.
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/default-repository.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/Copy-PortablePath"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "default-repository"
|
|
||||||
description = "Copy Portable Path is a lightweight PowerShell utility that adds two context menu items to Windows Explorer — Copy Relative Path and Copy Absolute Path — both using forward slashes for cross-platform compatibility. Supports multiple selections, optional MSYS/WSL /c/... style, and installs per-user without admin rights."
|
|
||||||
sync_timestamp = "2026-04-02T15:20:12+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/default-repository.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 11
|
|
||||||
updated_files = 31
|
|
||||||
skipped_files = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "updated" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/Copy-PortablePath/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
|
|
||||||
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
|
|
||||||
" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci", "code-quality"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "master"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,400 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoDoliMods
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:19:29+00:00
|
|
||||||
* Platform : crm-platform
|
|
||||||
* Description: The DoliMods is the repository of the Dolibarr ERP CRM modules, developed by the DoliCloud.com team.
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/crm-platform.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/MokoDoliMods"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "crm-platform"
|
|
||||||
description = "The DoliMods is the repository of the Dolibarr ERP CRM modules, developed by the DoliCloud.com team."
|
|
||||||
sync_timestamp = "2026-04-02T15:19:29+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/crm-platform.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 3
|
|
||||||
updated_files = 40
|
|
||||||
skipped_files = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "CONTRIBUTING.md" action = "updated" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "updated" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoDoliMods/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
|
|
||||||
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
|
|
||||||
" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Dolibarr Platform Structure Definition
|
|
||||||
* Standard repository structure for the full Dolibarr ERP/CRM installation
|
|
||||||
*
|
|
||||||
* This is distinct from crm-module — it defines the ENTIRE Dolibarr platform
|
|
||||||
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
|
|
||||||
* publish-to-mokodolimods workflow.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Dolibarr Platform"
|
|
||||||
description = "Full Dolibarr ERP/CRM installation — htdocs/ root, not a module"
|
|
||||||
repository_type = "crm-platform"
|
|
||||||
platform = "dolibarr"
|
|
||||||
last_updated = "2026-03-31T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Developer-focused documentation"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
audience = "contributor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "GPL-3.0-or-later license file"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/LICENSE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer package definition"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "phpstan.neon"
|
|
||||||
extension = "neon"
|
|
||||||
description = "PHPStan static analysis configuration"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/phpstan.neon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
extension = ""
|
|
||||||
description = "Build automation targets"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/Makefile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src/.ftpignore"
|
|
||||||
extension = ""
|
|
||||||
description = "Files excluded from SFTP deployment"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/ftp_ignore"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".mokostandards"
|
|
||||||
extension = ""
|
|
||||||
description = "MokoStandards platform identifier"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/mokostandards.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "htdocs"
|
|
||||||
path = "htdocs"
|
|
||||||
description = "Dolibarr web root — entire platform"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Developer and technical documentation"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains technical documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "update-server.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Dolibarr update server (update.txt) documentation"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-update-server-dolibarr.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub configuration"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to dev server (htdocs/)"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to demo server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to release staging server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on minor version"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: retired workflows, stale branches"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue on dev/rc branch creation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repo_health.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/dolibarr/repo_health.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dolibarr_issue.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dolibarr_module_id_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output "crm_platform_structure" {
|
|
||||||
description = "Dolibarr Platform repository structure definition"
|
|
||||||
value = local.repository_structure
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,400 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoDolibarr
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:42:39+00:00
|
|
||||||
* Platform : crm-platform
|
|
||||||
* Description: Dolibarr ERP CRM is a modern software package to manage your company or foundation\'s activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it\'s an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/crm-platform.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/MokoDolibarr"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "crm-platform"
|
|
||||||
description = "Dolibarr ERP CRM is a modern software package to manage your company or foundation\'s activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it\'s an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers."
|
|
||||||
sync_timestamp = "2026-04-02T15:42:39+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/crm-platform.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 16
|
|
||||||
updated_files = 29
|
|
||||||
skipped_files = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "CONTRIBUTING.md" action = "created" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" action = "created" },
|
|
||||||
{ path = "ROADMAP.md" action = "created" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "created" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoDolibarr/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
|
|
||||||
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
|
|
||||||
" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Dolibarr Platform Structure Definition
|
|
||||||
* Standard repository structure for the full Dolibarr ERP/CRM installation
|
|
||||||
*
|
|
||||||
* This is distinct from crm-module — it defines the ENTIRE Dolibarr platform
|
|
||||||
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
|
|
||||||
* publish-to-mokodolimods workflow.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Dolibarr Platform"
|
|
||||||
description = "Full Dolibarr ERP/CRM installation — htdocs/ root, not a module"
|
|
||||||
repository_type = "crm-platform"
|
|
||||||
platform = "dolibarr"
|
|
||||||
last_updated = "2026-03-31T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Developer-focused documentation"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
audience = "contributor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "GPL-3.0-or-later license file"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/LICENSE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer package definition"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "phpstan.neon"
|
|
||||||
extension = "neon"
|
|
||||||
description = "PHPStan static analysis configuration"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/phpstan.neon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
extension = ""
|
|
||||||
description = "Build automation targets"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/Makefile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src/.ftpignore"
|
|
||||||
extension = ""
|
|
||||||
description = "Files excluded from SFTP deployment"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/ftp_ignore"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".mokostandards"
|
|
||||||
extension = ""
|
|
||||||
description = "MokoStandards platform identifier"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/configs/mokostandards.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "htdocs"
|
|
||||||
path = "htdocs"
|
|
||||||
description = "Dolibarr web root — entire platform"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Developer and technical documentation"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains technical documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "update-server.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Dolibarr update server (update.txt) documentation"
|
|
||||||
required = true
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/docs/required/template-update-server-dolibarr.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub configuration"
|
|
||||||
required = true
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to dev server (htdocs/)"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to demo server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment to release staging server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on minor version"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: retired workflows, stale branches"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue on dev/rc branch creation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repo_health.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/dolibarr/repo_health.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dolibarr_issue.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dolibarr_module_id_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output "crm_platform_structure" {
|
|
||||||
description = "Dolibarr Platform repository structure definition"
|
|
||||||
value = local.repository_structure
|
|
||||||
}
|
|
||||||
@@ -1,734 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoISOUpdatePortable
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:33:45+00:00
|
|
||||||
* Platform : default-repository
|
|
||||||
* Description: A PortableApp that keeps ISOs of selected systems up to date.
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/default-repository.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/MokoISOUpdatePortable"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "default-repository"
|
|
||||||
description = "A PortableApp that keeps ISOs of selected systems up to date."
|
|
||||||
sync_timestamp = "2026-04-02T15:33:45+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/default-repository.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 11
|
|
||||||
updated_files = 31
|
|
||||||
skipped_files = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "updated" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoISOUpdatePortable/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
|
|
||||||
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
|
|
||||||
" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci", "code-quality"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "master"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,734 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoPerfectPublisher-Discord
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T21:05:10+00:00
|
|
||||||
* Platform : default-repository
|
|
||||||
* Description: A Perfect Publisher plugin to post to Discord
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/default-repository.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/MokoPerfectPublisher-Discord"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "default-repository"
|
|
||||||
description = "A Perfect Publisher plugin to post to Discord"
|
|
||||||
sync_timestamp = "2026-04-02T21:05:10+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/default-repository.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 11
|
|
||||||
updated_files = 31
|
|
||||||
skipped_files = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "updated" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoPerfectPublisher-Discord/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
|
|
||||||
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
|
|
||||||
" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci", "code-quality"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "master"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,734 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Client
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:40:35+00:00
|
|
||||||
* Platform : default-repository
|
|
||||||
* Description: A template repo for clients of Moko Consulting
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/default-repository.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/MokoStandards-Template-Client"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "default-repository"
|
|
||||||
description = "A template repo for clients of Moko Consulting"
|
|
||||||
sync_timestamp = "2026-04-02T15:40:35+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/default-repository.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 11
|
|
||||||
updated_files = 31
|
|
||||||
skipped_files = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "updated" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoStandards-Template-Client/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
|
|
||||||
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
|
|
||||||
" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci", "code-quality"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "master"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,734 +0,0 @@
|
|||||||
/**
|
|
||||||
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Generic
|
|
||||||
*
|
|
||||||
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:27:41+00:00
|
|
||||||
* Platform : default-repository
|
|
||||||
* Description: A repo template for a generic coding project according to MokoStandards
|
|
||||||
*
|
|
||||||
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
|
|
||||||
* To change what gets synced, edit api/definitions/default/default-repository.tf
|
|
||||||
* and re-run the bulk-repo-sync workflow.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
sync_record = {
|
|
||||||
metadata = {
|
|
||||||
repo = "mokoconsulting-tech/MokoStandards-Template-Generic"
|
|
||||||
default_branch = "main"
|
|
||||||
detected_platform = "default-repository"
|
|
||||||
description = "A repo template for a generic coding project according to MokoStandards"
|
|
||||||
sync_timestamp = "2026-04-02T15:27:41+00:00"
|
|
||||||
source_repo = "mokoconsulting-tech/MokoStandards"
|
|
||||||
base_definition = "api/definitions/default/default-repository.tf"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_stats = {
|
|
||||||
total_files = 53
|
|
||||||
created_files = 11
|
|
||||||
updated_files = 31
|
|
||||||
skipped_files = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
synced_files = [
|
|
||||||
{ path = "LICENSE" action = "updated" },
|
|
||||||
{ path = "Makefile" action = "updated" },
|
|
||||||
{ path = "composer.json" action = "updated" },
|
|
||||||
{ path = "docs/index.md" action = "updated" },
|
|
||||||
{ path = "docs/INSTALLATION.md" action = "updated" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-dev.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-demo.yml" action = "created" },
|
|
||||||
{ path = ".github/deploy-rs.yml" action = "created" },
|
|
||||||
{ path = ".github/sync-version-on-merge.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-release.yml" action = "created" },
|
|
||||||
{ path = ".github/repository-cleanup.yml" action = "created" },
|
|
||||||
{ path = ".github/auto-dev-issue.yml" action = "created" },
|
|
||||||
{ path = ".github/workflows/test.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/code-quality.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-release.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
|
|
||||||
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
|
|
||||||
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
|
|
||||||
{ path = ".github/CODEOWNERS" action = "updated" },
|
|
||||||
]
|
|
||||||
|
|
||||||
skipped_files = [
|
|
||||||
{ path = "README.md" reason = "README — never overwritten" },
|
|
||||||
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
|
|
||||||
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
|
|
||||||
{ path = "GOVERNANCE.md" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoStandards-Template-Generic/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
|
|
||||||
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
|
|
||||||
" },
|
|
||||||
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
|
|
||||||
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Base platform definition (reference copy) ----
|
|
||||||
/**
|
|
||||||
* Default Repository Structure Definition
|
|
||||||
* Default repository structure applicable to all repository types with minimal requirements
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
* Schema Version: 1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
repository_structure = {
|
|
||||||
metadata = {
|
|
||||||
name = "Default Repository Structure"
|
|
||||||
description = "Default repository structure applicable to all repository types with minimal requirements"
|
|
||||||
repository_type = "library"
|
|
||||||
platform = "multi-platform"
|
|
||||||
last_updated = "2026-01-16T00:00:00Z"
|
|
||||||
maintainer = "Moko Consulting"
|
|
||||||
version = "05.00.00"
|
|
||||||
schema_version = "1.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
root_files = [
|
|
||||||
{
|
|
||||||
name = "README.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project overview and documentation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-README.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "README.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-README.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "LICENSE"
|
|
||||||
extension = ""
|
|
||||||
description = "License file (GPL-3.0-or-later)"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/licenses"
|
|
||||||
source_filename = "GPL-3.0"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "LICENSE"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/licenses/GPL-3.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CHANGELOG.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Version history and changes"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CHANGELOG.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CHANGELOG.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CHANGELOG.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CONTRIBUTING.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Contribution guidelines"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-CONTRIBUTING.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CONTRIBUTING.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-CONTRIBUTING.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "SECURITY.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Security policy and vulnerability reporting"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-SECURITY.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "SECURITY.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/required/template-SECURITY.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODE_OF_CONDUCT.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Community code of conduct"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "contributor"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-CODE_OF_CONDUCT.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "CODE_OF_CONDUCT.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ROADMAP.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project roadmap with version goals and milestones"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-ROADMAP.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "ROADMAP.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-ROADMAP.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "GOVERNANCE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Project governance model and decision-making process"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
always_overwrite = false
|
|
||||||
protected = true
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/extra"
|
|
||||||
source_filename = "template-GOVERNANCE.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "GOVERNANCE.md"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/docs/extra/template-GOVERNANCE.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitignore"
|
|
||||||
extension = "gitignore"
|
|
||||||
description = "Git ignore patterns"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".gitattributes"
|
|
||||||
extension = "gitattributes"
|
|
||||||
description = "Git attributes configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".editorconfig"
|
|
||||||
extension = "editorconfig"
|
|
||||||
description = "Editor configuration for consistent coding style"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "Makefile"
|
|
||||||
description = "Build automation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
audience = "developer"
|
|
||||||
source_path = "templates/makefiles"
|
|
||||||
source_filename = "Makefile.generic.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "."
|
|
||||||
destination_filename = "Makefile"
|
|
||||||
create_path = false
|
|
||||||
template = "templates/makefiles/Makefile.generic.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "composer.json"
|
|
||||||
extension = "json"
|
|
||||||
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
|
|
||||||
required = true
|
|
||||||
always_overwrite = false
|
|
||||||
audience = "developer"
|
|
||||||
template = "templates/configs/composer.generic.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
directories = [
|
|
||||||
{
|
|
||||||
name = "docs"
|
|
||||||
path = "docs"
|
|
||||||
description = "Documentation directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains comprehensive project documentation"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "index.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Documentation index"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
template = "templates/docs/index.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "INSTALLATION.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Installation and setup instructions"
|
|
||||||
requirement_status = "required"
|
|
||||||
audience = "general"
|
|
||||||
source_path = "templates/docs/required"
|
|
||||||
source_filename = "template-INSTALLATION.md"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = "docs"
|
|
||||||
destination_filename = "INSTALLATION.md"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/docs/required/template-INSTALLATION.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "API.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "API documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ARCHITECTURE.md"
|
|
||||||
extension = "md"
|
|
||||||
description = "Architecture documentation"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "scripts"
|
|
||||||
path = "scripts"
|
|
||||||
description = "Repo-specific scripts — not managed by MokoStandards sync"
|
|
||||||
required = false
|
|
||||||
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "MokoStandards.override.xml"
|
|
||||||
extension = "xml"
|
|
||||||
description = "MokoStandards sync override configuration"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "src"
|
|
||||||
path = "src"
|
|
||||||
description = "Source code directory"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains application source code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "tests"
|
|
||||||
path = "tests"
|
|
||||||
description = "Test files"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
purpose = "Contains unit tests, integration tests, and test fixtures"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "unit"
|
|
||||||
path = "tests/unit"
|
|
||||||
description = "Unit tests"
|
|
||||||
requirement_status = "suggested"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "integration"
|
|
||||||
path = "tests/integration"
|
|
||||||
description = "Integration tests"
|
|
||||||
requirement_status = "optional"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = ".github"
|
|
||||||
path = ".github"
|
|
||||||
description = "GitHub-specific configuration"
|
|
||||||
requirement_status = "required"
|
|
||||||
purpose = "Contains GitHub Actions workflows and configuration"
|
|
||||||
subdirectories = [
|
|
||||||
{
|
|
||||||
name = "workflows"
|
|
||||||
path = ".github/workflows"
|
|
||||||
description = "GitHub Actions workflows"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "test.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Comprehensive testing workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "test.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "test.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/test.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "code-quality.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Code quality and linting workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "code-quality.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "code-quality.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/code-quality.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "codeql-analysis.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "CodeQL security analysis workflow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "codeql-analysis.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "codeql-analysis.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/codeql-analysis.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Deployment workflow"
|
|
||||||
requirement_status = "optional"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = "templates/workflows/generic"
|
|
||||||
source_filename = "deploy.yml.template"
|
|
||||||
source_type = "template"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "deploy.yml"
|
|
||||||
create_path = true
|
|
||||||
template = "templates/workflows/generic/deploy.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "release-cycle.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Release management workflow with automated release flow"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "release-cycle.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "release-cycle.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/release-cycle.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "standards-compliance.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "MokoStandards compliance validation"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
source_path = ".github/workflows"
|
|
||||||
source_filename = "standards-compliance.yml"
|
|
||||||
source_type = "copy"
|
|
||||||
destination_path = ".github/workflows"
|
|
||||||
destination_filename = "standards-compliance.yml"
|
|
||||||
create_path = true
|
|
||||||
template = ".github/workflows/standards-compliance.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise-firewall-setup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Enterprise firewall configuration for trusted domain access"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-dev.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the development server"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-dev.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-demo.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the demo server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-demo.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "deploy-rs.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "SFTP deployment of src/ to the release staging server on merge to main"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/deploy-rs.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "sync-version-on-merge.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-bump patch version on merge and propagate to all file headers"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-release.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create GitHub Release on push to main with version from README.md"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-release.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "repository-cleanup.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/repository-cleanup.yml.template"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "auto-dev-issue.yml"
|
|
||||||
extension = "yml"
|
|
||||||
description = "Auto-create tracking issue when a dev/** branch is pushed"
|
|
||||||
requirement_status = "required"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/workflows/shared/auto-dev-issue.yml.template"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "ISSUE_TEMPLATE"
|
|
||||||
path = ".github/ISSUE_TEMPLATE"
|
|
||||||
description = "GitHub issue templates synced from MokoStandards"
|
|
||||||
requirement_status = "required"
|
|
||||||
files = [
|
|
||||||
{
|
|
||||||
name = "config.yml"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/config.yml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "adr.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/adr.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "bug_report.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enterprise_support.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "feature_request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "firewall-request.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "question.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/question.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "request-license.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "rfc.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security.md"
|
|
||||||
always_overwrite = true
|
|
||||||
template = "templates/github/ISSUE_TEMPLATE/security.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "node_modules"
|
|
||||||
path = "node_modules"
|
|
||||||
description = "Node.js dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "vendor"
|
|
||||||
path = "vendor"
|
|
||||||
description = "PHP dependencies (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "build"
|
|
||||||
path = "build"
|
|
||||||
description = "Build artifacts (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "dist"
|
|
||||||
path = "dist"
|
|
||||||
description = "Distribution files (generated)"
|
|
||||||
requirement_status = "not-allowed"
|
|
||||||
purpose = "Generated directory that should not be committed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_requirements = {
|
|
||||||
secrets = [
|
|
||||||
{
|
|
||||||
name = "GH_TOKEN"
|
|
||||||
description = "Org-level GitHub PAT — configure in org Actions secrets"
|
|
||||||
required = true
|
|
||||||
scope = "organisation"
|
|
||||||
used_in = "GitHub Actions workflows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "CODECOV_TOKEN"
|
|
||||||
description = "Codecov upload token for code coverage reporting"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
used_in = "CI workflow code coverage step"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
variables = [
|
|
||||||
{
|
|
||||||
name = "NODE_VERSION"
|
|
||||||
description = "Node.js version for CI/CD"
|
|
||||||
default_value = "18"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "PYTHON_VERSION"
|
|
||||||
description = "Python version for CI/CD"
|
|
||||||
default_value = "3.9"
|
|
||||||
required = false
|
|
||||||
scope = "repository"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
branch_protections = [
|
|
||||||
{
|
|
||||||
branch_pattern = "main"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci", "code-quality"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
branch_pattern = "master"
|
|
||||||
require_pull_request = true
|
|
||||||
required_approvals = 1
|
|
||||||
require_code_owner_review = false
|
|
||||||
dismiss_stale_reviews = true
|
|
||||||
require_status_checks = true
|
|
||||||
required_status_checks = ["ci"]
|
|
||||||
enforce_admins = false
|
|
||||||
restrict_pushes = true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
repository_settings = {
|
|
||||||
has_issues = true
|
|
||||||
has_projects = true
|
|
||||||
has_wiki = false
|
|
||||||
has_discussions = false
|
|
||||||
allow_merge_commit = true
|
|
||||||
allow_squash_merge = true
|
|
||||||
allow_rebase_merge = false
|
|
||||||
delete_branch_on_merge = true
|
|
||||||
allow_auto_merge = false
|
|
||||||
}
|
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
name = "bug"
|
|
||||||
color = "d73a4a"
|
|
||||||
description = "Something isn't working"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "enhancement"
|
|
||||||
color = "a2eeef"
|
|
||||||
description = "New feature or request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "documentation"
|
|
||||||
color = "0075ca"
|
|
||||||
description = "Improvements or additions to documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "security"
|
|
||||||
color = "ee0701"
|
|
||||||
description = "Security vulnerability or concern"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user