Public Access
Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98af4abd01 | |||
| baf5f56ca2 | |||
| 6b0822aa0f | |||
| 50f253d444 | |||
| dddf89d485 | |||
| ee78f5ffc8 | |||
| aff3fef279 | |||
| 7f0e5e6226 | |||
| 79277e2e20 | |||
| 3e3e21d779 | |||
| 7ab69f674f | |||
| f1379c0245 | |||
| 5fc9040493 | |||
| 1bda63c38a | |||
| 1e9536dd58 | |||
| 52e960de33 | |||
| 46fc2e7235 | |||
| 6c402fcc33 | |||
| 4a94f0d24a | |||
| c5601632fc | |||
| 3330646e33 | |||
| 33e0dbb598 | |||
| cf52b79244 | |||
| c38622d4c3 | |||
| b0c126e990 | |||
| 44457302df | |||
| ccb30937b7 | |||
| 36b88872b1 | |||
| d19078c603 | |||
| b2349f9977 | |||
| 688b9daacd | |||
| d99a8489ca | |||
| 641505caeb | |||
| 91a662ed6d | |||
| 2222877895 | |||
| 55f3cdcbdd | |||
| eaca46e7cf | |||
| ee49474f35 | |||
| a48be3ff87 | |||
| 51f8fcb480 | |||
| dfb3ca876b | |||
| e2e9f6d33b | |||
| 9f0546c1a8 | |||
| 3167233b7c | |||
| 94e6084e0f | |||
| 55a82d7d21 | |||
| 88acf57278 | |||
| ac293fd038 | |||
| 20c036f913 | |||
| 062b33b395 | |||
| 2d8bb5e967 | |||
| a172b21b9b | |||
| 2ce8caad88 | |||
| 0b97aa06c9 | |||
| f2fb64ec5a | |||
| eabc96b506 | |||
| ed54dd5153 | |||
| 40ebb4774c | |||
| bb2c34abbb | |||
| 5680b32e31 | |||
| 88fa860080 | |||
| 0d9c64cd28 | |||
| 3dbd221c69 | |||
| 2cedc0b94e | |||
| 94fde481d2 | |||
| b5c38d76e8 | |||
| ce0502e788 | |||
| cf29777daa | |||
| 61d52ce1cb | |||
| 16305623cc | |||
| e5768d5e44 | |||
| 9342bdf373 | |||
| dad002cc08 | |||
| 37fdef1323 | |||
| 845a377045 | |||
| 1a83a2abd5 | |||
| 9577ea6058 | |||
| c113f0437e | |||
| bfaab8a79e | |||
| 84e54c7567 | |||
| f270ce6a78 | |||
| 7b45f7d92b | |||
| 9859a06a09 | |||
| 926142afa9 | |||
| 6e5a3b902c | |||
| 08130d1bf3 | |||
| 4009d68a7a | |||
| 2312c95dba | |||
| 7a06bcf403 | |||
| a8baf355c4 | |||
| 9520ce3bb8 | |||
| 120dbe3be2 | |||
| 657e6013de | |||
| c7a26c1ce1 | |||
| bdbabd9886 | |||
| e061537404 | |||
| 4637dab159 | |||
| b885f12ece | |||
| e99881ad65 | |||
| fd3c847fb1 | |||
| 424fe658ba | |||
| 0ff87f3631 | |||
| 3164f21ba9 | |||
| 713cb61efc | |||
| fa342a278b | |||
| d91baf996e | |||
| 2100a3bec7 | |||
| 8d772df3e7 | |||
| 3972e4fb84 | |||
| 86b8449eba | |||
| 601451867d | |||
| 09ffcdce49 | |||
| a49e0d7b2a | |||
| bc1a111775 | |||
| 98c41c3a53 | |||
| 52ebcde1ff | |||
| ded17c34b8 | |||
| 1b79de20a9 | |||
| bba1cbc845 | |||
| 3f956f5661 | |||
| 9036d20898 | |||
| 7c1edcf813 | |||
| fec5a48b4a | |||
| 468b962aaa | |||
| cc66a79fea | |||
| 660ae5bed4 | |||
| 5ffaf95d20 | |||
| ddfb479bc2 | |||
| 13e2729124 | |||
| 05ee59d75e | |||
| c4bb08073b | |||
| b32556fdef | |||
| c57a7e4b96 | |||
| 5846ccc3e8 | |||
| 6ba0208571 | |||
| 759b04e088 | |||
| 4dfa2d9722 | |||
| 590ad7337a | |||
| c607966e83 | |||
| 97f2226288 | |||
| b1f2c391cb | |||
| 7e4d88eea5 | |||
| 9fdd6c5cf9 | |||
| 113af457d9 | |||
| abd67f6c43 | |||
| d3dce5d24e | |||
| c9592d8e78 | |||
| 6bf6251705 | |||
| 520acd4fee | |||
| a6c4fe0688 | |||
| 3c420284e3 | |||
| 66543c14d3 | |||
| d9908e264a | |||
| 9206f716e1 | |||
| 84633e22e5 | |||
| 510d1248ab | |||
| 8e30fa54b3 | |||
| 1aaec7d8ff | |||
| 5efdaf6c05 | |||
| 448e11568b | |||
| dce0fe0994 | |||
| 2dc115327b | |||
| 4715dba7fc | |||
| 5ab1782715 | |||
| 08db79da17 | |||
| c94564cec6 | |||
| ac52a697e5 | |||
| 57b458b79a | |||
| 2a84b38194 | |||
| 9d9b78b4e9 | |||
| 092543db1c | |||
| 2286514c59 | |||
| 6993f30513 | |||
| 0b30cc5a6d | |||
| 7b0c239dd2 | |||
| cf278cf914 | |||
| fdeacc73c5 | |||
| 117bba7765 | |||
| 65a11a19ac | |||
| fae2eab41b | |||
| f91b05287a | |||
| 43fd64a15b | |||
| 15074fb0ae | |||
| 415b5403be | |||
| 84fdb39d20 | |||
| 44b9e991b5 | |||
| 9b647b2540 | |||
| cedf10bdff | |||
| 8422292b46 | |||
| b1f3487a43 |
@@ -38,7 +38,7 @@ composer check # Run all checks
|
|||||||
| `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) |
|
||||||
| `templates/` | Universal templates, configs, governance schema |
|
| `templates/` | Universal templates, configs, governance schema |
|
||||||
| `.mokogitea/workflows/` | CI/CD workflows (Gitea Actions) |
|
| `.mokogit/workflows/` | CI/CD workflows (MokoGIT Actions) |
|
||||||
| `bin/moko` | Unified CLI dispatcher — `php bin/moko <command>` |
|
| `bin/moko` | Unified CLI dispatcher — `php bin/moko <command>` |
|
||||||
| `monitoring/sites.json` | Sites list for mcp_mokomonitor |
|
| `monitoring/sites.json` | Sites list for mcp_mokomonitor |
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ After adding a CLI tool, register it in `bin/moko` COMMAND_MAP.
|
|||||||
|
|
||||||
### Platform Adapters
|
### Platform Adapters
|
||||||
|
|
||||||
- `MokoGiteaAdapter` — git.mokoconsulting.tech (primary)
|
- `MokoGITAdapter` — git.mokoconsulting.tech (primary)
|
||||||
- `GitHubAdapter` — github.com mirrors
|
- `GitHubAdapter` — github.com mirrors
|
||||||
|
|
||||||
### Plugin System
|
### Plugin System
|
||||||
@@ -70,7 +70,7 @@ PHPStan runs with `--memory-limit=512M`. CI enforces PHPCS errors; PHPStan is `c
|
|||||||
|
|
||||||
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
|
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
|
||||||
- **Attribution**: `Authored-by: Moko Consulting`
|
- **Attribution**: `Authored-by: Moko Consulting`
|
||||||
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
|
- **Workflow directory**: `.mokogit/` (not `.gitea/` or `.github/`)
|
||||||
- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files
|
- **Wiki**: documentation lives in the MokoGIT wiki, not `docs/` files
|
||||||
- **New CLI tools**: extend `CliFramework`, not `CLIApp` (legacy)
|
- **New CLI tools**: extend `CliFramework`, not `CLIApp` (legacy)
|
||||||
- **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)
|
- **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# 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
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Git.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/branch-protection.yml
|
# PATH: /.gitea/workflows/branch-protection.yml
|
||||||
@@ -42,8 +42,8 @@ on:
|
|||||||
default: ''
|
default: ''
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: https://git.mokoconsulting.tech
|
GIT_URL: https://git.mokoconsulting.tech
|
||||||
GITEA_ORG: MokoConsulting
|
GIT_ORG: MokoConsulting
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -57,12 +57,12 @@ jobs:
|
|||||||
- name: Determine target repos
|
- name: Determine target repos
|
||||||
id: repos
|
id: repos
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
API="${GITEA_URL}/api/v1"
|
API="${GIT_URL}/api/v1"
|
||||||
|
|
||||||
# Platform/standards/infra repos to exclude
|
# Platform/standards/infra repos to exclude
|
||||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokocli MokoTesting"
|
EXCLUDE="git-org-config org-profile git-private .mokogit-private mokocli MokoTesting"
|
||||||
EXCLUDE="$EXCLUDE MokoCli-Template-Client MokoCli-Template-Dolibarr MokoCli-Template-Generic MokoCli-Template-Joomla MokoDoliProjTemplate"
|
EXCLUDE="$EXCLUDE MokoCli-Template-Client MokoCli-Template-Dolibarr MokoCli-Template-Generic MokoCli-Template-Joomla MokoDoliProjTemplate"
|
||||||
|
|
||||||
if [ -n "${{ inputs.repos }}" ]; then
|
if [ -n "${{ inputs.repos }}" ]; then
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
while true; do
|
while true; do
|
||||||
BATCH=$(curl -sS \
|
BATCH=$(curl -sS \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \
|
"${API}/orgs/${GIT_ORG}/repos?page=${PAGE}&limit=50" \
|
||||||
| jq -r '.[].name // empty')
|
| jq -r '.[].name // empty')
|
||||||
[ -z "$BATCH" ] && break
|
[ -z "$BATCH" ] && break
|
||||||
REPOS="$REPOS $BATCH"
|
REPOS="$REPOS $BATCH"
|
||||||
@@ -105,10 +105,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Apply protection rules
|
- name: Apply protection rules
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
DRY_RUN: ${{ inputs.dry_run || 'false' }}
|
DRY_RUN: ${{ inputs.dry_run || 'false' }}
|
||||||
run: |
|
run: |
|
||||||
API="${GITEA_URL}/api/v1"
|
API="${GIT_URL}/api/v1"
|
||||||
REPOS="${{ steps.repos.outputs.repos }}"
|
REPOS="${{ steps.repos.outputs.repos }}"
|
||||||
|
|
||||||
SUCCESS=0
|
SUCCESS=0
|
||||||
@@ -124,7 +124,8 @@ jobs:
|
|||||||
"rule_name": "main",
|
"rule_name": "main",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push": false,
|
"enable_force_push": false,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push_allowlist": false,
|
||||||
"force_push_allowlist_usernames": [],
|
"force_push_allowlist_usernames": [],
|
||||||
@@ -140,10 +141,12 @@ jobs:
|
|||||||
"rule_name": "dev",
|
"rule_name": "dev",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
"enable_force_push": false,
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push": true,
|
||||||
"force_push_allowlist_usernames": [],
|
"enable_force_push_allowlist": true,
|
||||||
|
"force_push_allowlist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"force_push_allowlist_actions_user": true,
|
||||||
"enable_merge_whitelist": false,
|
"enable_merge_whitelist": false,
|
||||||
"required_approvals": 0,
|
"required_approvals": 0,
|
||||||
"block_on_rejected_reviews": false,
|
"block_on_rejected_reviews": false,
|
||||||
@@ -154,10 +157,12 @@ jobs:
|
|||||||
"rule_name": "rc",
|
"rule_name": "rc",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
"enable_force_push": false,
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push": true,
|
||||||
"force_push_allowlist_usernames": [],
|
"enable_force_push_allowlist": true,
|
||||||
|
"force_push_allowlist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"force_push_allowlist_actions_user": true,
|
||||||
"enable_merge_whitelist": false,
|
"enable_merge_whitelist": false,
|
||||||
"required_approvals": 0,
|
"required_approvals": 0,
|
||||||
"block_on_rejected_reviews": false,
|
"block_on_rejected_reviews": false,
|
||||||
@@ -168,7 +173,8 @@ jobs:
|
|||||||
"rule_name": "beta",
|
"rule_name": "beta",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push": false,
|
"enable_force_push": false,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push_allowlist": false,
|
||||||
"force_push_allowlist_usernames": [],
|
"force_push_allowlist_usernames": [],
|
||||||
@@ -182,7 +188,8 @@ jobs:
|
|||||||
"rule_name": "alpha",
|
"rule_name": "alpha",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push": false,
|
"enable_force_push": false,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push_allowlist": false,
|
||||||
"force_push_allowlist_usernames": [],
|
"force_push_allowlist_usernames": [],
|
||||||
@@ -215,7 +222,7 @@ jobs:
|
|||||||
curl -sS -o /dev/null -w "" \
|
curl -sS -o /dev/null -w "" \
|
||||||
-X DELETE \
|
-X DELETE \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
|
"${API}/repos/${GIT_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
|
||||||
|
|
||||||
# Create rule
|
# Create rule
|
||||||
RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||||
@@ -223,7 +230,7 @@ jobs:
|
|||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$RULE" \
|
-d "$RULE" \
|
||||||
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections")
|
"${API}/repos/${GIT_ORG}/${REPO}/branch_protections")
|
||||||
|
|
||||||
HTTP=$(echo "$RESPONSE" | tail -1)
|
HTTP=$(echo "$RESPONSE" | tail -1)
|
||||||
BODY=$(echo "$RESPONSE" | sed '$d')
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# 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
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Git.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/bulk-repo-sync.yml
|
# PATH: /.gitea/workflows/bulk-repo-sync.yml
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
id: args
|
id: args
|
||||||
run: |
|
run: |
|
||||||
ARGS="--org MokoConsulting"
|
ARGS="--org MokoConsulting"
|
||||||
if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ gitea.event_name }}" = "schedule" ]; then
|
if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ git.event_name }}" = "schedule" ]; then
|
||||||
ARGS="$ARGS --dry-run"
|
ARGS="$ARGS --dry-run"
|
||||||
fi
|
fi
|
||||||
if [ -n "${{ inputs.repos }}" ]; then
|
if [ -n "${{ inputs.repos }}" ]; then
|
||||||
@@ -84,18 +84,18 @@ jobs:
|
|||||||
echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}"
|
echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}"
|
||||||
php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log
|
php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||||
GIT_PLATFORM: gitea
|
GIT_PLATFORM: git
|
||||||
GITEA_URL: https://git.mokoconsulting.tech
|
GIT_URL: https://git.mokoconsulting.tech
|
||||||
GITEA_ORG: MokoConsulting
|
GIT_ORG: MokoConsulting
|
||||||
|
|
||||||
- name: Commit Updated Definitions
|
- name: Commit Updated Definitions
|
||||||
if: success() && inputs.dry_run != 'true'
|
if: success() && inputs.dry_run != 'true'
|
||||||
run: |
|
run: |
|
||||||
if [ -n "$(git status --porcelain definitions/sync/)" ]; then
|
if [ -n "$(git status --porcelain definitions/sync/)" ]; then
|
||||||
git config user.name "gitea-actions[bot]"
|
git config user.name "git-actions[bot]"
|
||||||
git config user.email "gitea-actions[bot]@git.mokoconsulting.tech"
|
git config user.email "git-actions[bot]@git.mokoconsulting.tech"
|
||||||
git add definitions/sync/*.def.tf
|
git add definitions/sync/*.def.tf
|
||||||
git commit -m "chore: update synced repository definitions" || true
|
git commit -m "chore: update synced repository definitions" || true
|
||||||
git push || true
|
git push || true
|
||||||
@@ -112,9 +112,9 @@ jobs:
|
|||||||
bash automation/enforce_tags.sh || echo "Tag enforcement had errors (non-fatal)"
|
bash automation/enforce_tags.sh || echo "Tag enforcement had errors (non-fatal)"
|
||||||
fi
|
fi
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
GITEA_URL: https://git.mokoconsulting.tech
|
GIT_URL: https://git.mokoconsulting.tech
|
||||||
GITEA_ORG: MokoConsulting
|
GIT_ORG: MokoConsulting
|
||||||
|
|
||||||
- name: Upload Sync Log
|
- name: Upload Sync Log
|
||||||
if: always() && github.server_url == 'https://github.com'
|
if: always() && github.server_url == 'https://github.com'
|
||||||
@@ -124,7 +124,7 @@ jobs:
|
|||||||
path: /tmp/bulk_sync.log
|
path: /tmp/bulk_sync.log
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- name: Log Summary (Gitea)
|
- name: Log Summary (Git)
|
||||||
if: always() && github.server_url != 'https://github.com'
|
if: always() && github.server_url != 'https://github.com'
|
||||||
run: |
|
run: |
|
||||||
if [ -f /tmp/bulk_sync.log ]; then
|
if [ -f /tmp/bulk_sync.log ]; then
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Git.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/renovate.yml
|
# PATH: /.gitea/workflows/renovate.yml
|
||||||
@@ -41,8 +41,8 @@ on:
|
|||||||
default: false
|
default: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: https://git.mokoconsulting.tech
|
GIT_URL: https://git.mokoconsulting.tech
|
||||||
GITEA_ORG: MokoConsulting
|
GIT_ORG: MokoConsulting
|
||||||
RENOVATE_VERSION: '39'
|
RENOVATE_VERSION: '39'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -57,11 +57,11 @@ jobs:
|
|||||||
- name: Determine target repos
|
- name: Determine target repos
|
||||||
id: repos
|
id: repos
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
API="${GITEA_URL}/api/v1"
|
API="${GIT_URL}/api/v1"
|
||||||
|
|
||||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokocli MokoTesting"
|
EXCLUDE="git-org-config org-profile git-private .mokogit-private mokocli MokoTesting"
|
||||||
EXCLUDE="$EXCLUDE MokoCli-Template-Client MokoCli-Template-Dolibarr MokoCli-Template-Generic MokoCli-Template-Joomla MokoDoliProjTemplate"
|
EXCLUDE="$EXCLUDE MokoCli-Template-Client MokoCli-Template-Dolibarr MokoCli-Template-Generic MokoCli-Template-Joomla MokoDoliProjTemplate"
|
||||||
|
|
||||||
if [ -n "${{ inputs.repos }}" ]; then
|
if [ -n "${{ inputs.repos }}" ]; then
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
while true; do
|
while true; do
|
||||||
BATCH=$(curl -sS \
|
BATCH=$(curl -sS \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \
|
"${API}/orgs/${GIT_ORG}/repos?page=${PAGE}&limit=50" \
|
||||||
| jq -r '.[].name // empty')
|
| jq -r '.[].name // empty')
|
||||||
[ -z "$BATCH" ] && break
|
[ -z "$BATCH" ] && break
|
||||||
REPOS="$REPOS $BATCH"
|
REPOS="$REPOS $BATCH"
|
||||||
@@ -94,9 +94,9 @@ jobs:
|
|||||||
REPO_LIST=""
|
REPO_LIST=""
|
||||||
for REPO in $REPOS; do
|
for REPO in $REPOS; do
|
||||||
if [ -n "$REPO_LIST" ]; then
|
if [ -n "$REPO_LIST" ]; then
|
||||||
REPO_LIST="${REPO_LIST},${GITEA_ORG}/${REPO}"
|
REPO_LIST="${REPO_LIST},${GIT_ORG}/${REPO}"
|
||||||
else
|
else
|
||||||
REPO_LIST="${GITEA_ORG}/${REPO}"
|
REPO_LIST="${GIT_ORG}/${REPO}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -107,8 +107,8 @@ jobs:
|
|||||||
- name: Run Renovate
|
- name: Run Renovate
|
||||||
if: steps.repos.outputs.repo_list != ''
|
if: steps.repos.outputs.repo_list != ''
|
||||||
env:
|
env:
|
||||||
RENOVATE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
RENOVATE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
RENOVATE_PLATFORM: gitea
|
RENOVATE_PLATFORM: git
|
||||||
RENOVATE_ENDPOINT: ${{ env.GITEA_URL }}/api/v1
|
RENOVATE_ENDPOINT: ${{ env.GITEA_URL }}/api/v1
|
||||||
RENOVATE_GIT_AUTHOR: 'Renovate Bot <renovate@mokoconsulting.tech>'
|
RENOVATE_GIT_AUTHOR: 'Renovate Bot <renovate@mokoconsulting.tech>'
|
||||||
RENOVATE_REPOSITORIES: ${{ steps.repos.outputs.repo_list }}
|
RENOVATE_REPOSITORIES: ${{ steps.repos.outputs.repo_list }}
|
||||||
@@ -116,8 +116,8 @@ jobs:
|
|||||||
LOG_LEVEL: info
|
LOG_LEVEL: info
|
||||||
run: |
|
run: |
|
||||||
npx --yes renovate@${RENOVATE_VERSION} \
|
npx --yes renovate@${RENOVATE_VERSION} \
|
||||||
--platform=gitea \
|
--platform=git \
|
||||||
--endpoint="${GITEA_URL}/api/v1" \
|
--endpoint="${GIT_URL}/api/v1" \
|
||||||
--token="${RENOVATE_TOKEN}" \
|
--token="${RENOVATE_TOKEN}" \
|
||||||
--git-author="Renovate Bot <renovate@mokoconsulting.tech>" \
|
--git-author="Renovate Bot <renovate@mokoconsulting.tech>" \
|
||||||
--autodiscover=false \
|
--autodiscover=false \
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Git.Workflow
|
||||||
# INGROUP: mokocli.Maintenance
|
# INGROUP: mokocli.Maintenance
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/sync-wikis.yml
|
# PATH: /.gitea/workflows/sync-wikis.yml
|
||||||
# BRIEF: Daily sync of all Gitea wikis to consolidated GitHub wiki repo
|
# BRIEF: Daily sync of all Git wikis to consolidated GitHub wiki repo
|
||||||
|
|
||||||
name: Sync Wikis to GitHub
|
name: Sync Wikis to GitHub
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
- name: Sync all wikis
|
- name: Sync all wikis
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
run: |
|
run: |
|
||||||
if [ -z "$GH_TOKEN" ]; then
|
if [ -z "$GH_TOKEN" ]; then
|
||||||
echo "::error::GH_TOKEN secret not set"
|
echo "::error::GH_TOKEN secret not set"
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: mokocli.Release
|
# INGROUP: MokoCLI.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
# PATH: /.mokogit/workflows/auto-bump.yml
|
||||||
# VERSION: 09.02.00
|
# VERSION: 09.02.00
|
||||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -34,16 +34,17 @@ jobs:
|
|||||||
if: >-
|
if: >-
|
||||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
!startsWith(github.event.head_commit.message, 'Merge pull request') &&
|
||||||
|
!startsWith(github.event.repository.name, 'Template-')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Setup MokoCLI tools
|
||||||
run: |
|
run: |
|
||||||
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
|
||||||
@@ -52,7 +53,7 @@ jobs:
|
|||||||
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
else
|
else
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
"https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||||
/tmp/mokocli
|
/tmp/mokocli
|
||||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
@@ -62,5 +63,5 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
php ${MOKO_CLI}/version_auto_bump.php \
|
php ${MOKO_CLI}/version_auto_bump.php \
|
||||||
--path . --branch "${GITHUB_REF_NAME}" \
|
--path . --branch "${GITHUB_REF_NAME}" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" \
|
||||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
--repo-url "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
@@ -3,18 +3,18 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: mokocli.Release
|
# INGROUP: MokoCLI.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
# PATH: /.mokogit/workflows/auto-release.yml
|
||||||
# VERSION: 05.00.00
|
# VERSION: 05.01.00
|
||||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
# BRIEF: Universal build & release � detects platform from metadata API
|
||||||
#
|
#
|
||||||
# +=======================================================================+
|
# +=======================================================================+
|
||||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||||
# +=======================================================================+
|
# +=======================================================================+
|
||||||
# | |
|
# | |
|
||||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
# | Reads metadata API (joomla|dolibarr|generic) to branch logic. |
|
||||||
# | |
|
# | |
|
||||||
# | Platform-specific: |
|
# | Platform-specific: |
|
||||||
# | joomla: XML manifest, type-prefixed packages |
|
# | joomla: XML manifest, type-prefixed packages |
|
||||||
@@ -31,7 +31,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '.mokogitea/workflows/**'
|
- '.mokogit/workflows/**'
|
||||||
- '*.md'
|
- '*.md'
|
||||||
- 'wiki/**'
|
- 'wiki/**'
|
||||||
- '.editorconfig'
|
- '.editorconfig'
|
||||||
@@ -52,9 +52,9 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -64,21 +64,26 @@ jobs:
|
|||||||
promote-rc:
|
promote-rc:
|
||||||
name: Promote to RC
|
name: Promote to RC
|
||||||
runs-on: release
|
runs-on: release
|
||||||
|
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||||
if: >-
|
if: >-
|
||||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
!startsWith(github.event.repository.name, 'Template-') &&
|
||||||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
|
(
|
||||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||||
|
(github.event.action == 'synchronize' && github.event.pull_request.merged != 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.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Setup MokoCLI tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
@@ -99,30 +104,68 @@ jobs:
|
|||||||
|
|
||||||
- name: Rename branch to rc
|
- name: Rename branch to rc
|
||||||
run: |
|
run: |
|
||||||
php ${MOKO_CLI}/branch_rename.php \
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
AUTH="Authorization: token ${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
FROM="${{ github.event.pull_request.head.ref || 'dev' }}"
|
||||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
PR="${{ github.event.pull_request.number }}"
|
||||||
--pr "${{ github.event.pull_request.number }}"
|
|
||||||
|
# Resolve the source branch HEAD commit.
|
||||||
|
SRC_JSON=$(curl -sf -H "$AUTH" "${API_BASE}/branches/${FROM}") \
|
||||||
|
|| { echo "::error::Source branch ${FROM} not found"; exit 1; }
|
||||||
|
SRC_SHA=$(printf '%s' "$SRC_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin)['commit']['id'])" 2>/dev/null || true)
|
||||||
|
[ -n "$SRC_SHA" ] || { echo "::error::Could not resolve HEAD of ${FROM}"; exit 1; }
|
||||||
|
|
||||||
|
# Point rc at the source commit via git push. Git's git/refs PATCH API
|
||||||
|
# returns HTTP 405 on ANY protected branch (force or not, even for a user in
|
||||||
|
# the force-push allowlist), so it cannot move a protected rc. git push honors
|
||||||
|
# the push + force-push allowlists and creates rc if it is absent.
|
||||||
|
PUSH_URL="https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@${MOKOGIT_URL#https://}/${GIT_ORG}/${GIT_REPO}.git"
|
||||||
|
git config --global user.name "mokogit-actions[bot]"
|
||||||
|
git config --global user.email "actions@mokoconsulting.tech"
|
||||||
|
git fetch --no-tags "$PUSH_URL" "${FROM}"
|
||||||
|
git push --force "$PUSH_URL" "FETCH_HEAD:refs/heads/rc" \
|
||||||
|
|| { echo "::error::Failed to point rc at ${FROM} (${SRC_SHA}) via git push"; exit 1; }
|
||||||
|
echo "rc set to ${FROM} (${SRC_SHA})"
|
||||||
|
|
||||||
|
# Repoint the PR at rc, then delete the old source branch (non-fatal).
|
||||||
|
if [ -n "$PR" ]; then
|
||||||
|
curl -s -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/pulls/${PR}" -d '{"head":"rc"}' >/dev/null || true
|
||||||
|
fi
|
||||||
|
# Never delete permanent branches (dev/main/rc/...); only ephemeral feature branches.
|
||||||
|
case "$FROM" in
|
||||||
|
dev|main|master|rc|stable|production|release|develop|staging|beta|alpha)
|
||||||
|
echo "Keeping permanent branch ${FROM} (not deleting)" ;;
|
||||||
|
*)
|
||||||
|
curl -s -X DELETE -H "$AUTH" "${API_BASE}/branches/${FROM}" >/dev/null || true ;;
|
||||||
|
esac
|
||||||
|
echo "Renamed ${FROM} -> rc"
|
||||||
|
|
||||||
|
- name: Trigger RC deploy
|
||||||
|
run: |
|
||||||
|
# Workflow-token pushes do NOT wake downstream workflows; dispatch deploy-rc explicitly.
|
||||||
|
curl -sf -X POST -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \n -H "Content-Type: application/json" \n "${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}/actions/workflows/deploy-rc.yml/dispatches" \n -d '{"ref":"rc"}' \n && echo "Dispatched deploy-rc on rc" \n || echo "::warning::deploy-rc dispatch failed (no deploy-rc.yml on rc? non-go platform)"
|
||||||
|
|
||||||
- name: Checkout rc and configure git
|
- name: Checkout rc and configure git
|
||||||
run: |
|
run: |
|
||||||
git fetch origin rc
|
git fetch origin rc
|
||||||
git checkout rc
|
git checkout rc
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "mokogit-actions[bot]"
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
- name: Publish RC release
|
- name: Publish RC release
|
||||||
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
php ${MOKO_CLI}/release_publish.php \
|
php ${MOKO_CLI}/release_publish.php \
|
||||||
--path . --stability rc --bump minor --branch rc \
|
--path . --stability rc --bump minor --branch rc \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
--token "${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
- name: Update RC release notes from CHANGELOG.md
|
- name: Update RC release notes from CHANGELOG.md
|
||||||
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog
|
# Extract [Unreleased] section from changelog
|
||||||
NOTES=""
|
NOTES=""
|
||||||
@@ -163,22 +206,27 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
name: Build & Release Pipeline
|
name: Build & Release Pipeline
|
||||||
runs-on: release
|
runs-on: release
|
||||||
|
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||||
if: >-
|
if: >-
|
||||||
github.event.pull_request.merged == true ||
|
!startsWith(github.event.repository.name, 'Template-') &&
|
||||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
(
|
||||||
|
github.event.pull_request.merged == 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.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Configure git for bot pushes
|
- name: Configure git for bot pushes
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "mokogit-actions[bot]"
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
- name: Check for merge conflict markers
|
- name: Check for merge conflict markers
|
||||||
run: |
|
run: |
|
||||||
@@ -193,9 +241,9 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "No conflict markers found"
|
echo "No conflict markers found"
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Setup MokoCLI tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||||
run: |
|
run: |
|
||||||
@@ -242,7 +290,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
php ${MOKO_CLI}/release_publish.php \
|
php ${MOKO_CLI}/release_publish.php \
|
||||||
--path . --stability stable ${BUMP_FLAG} --branch main \
|
--path . --stability stable ${BUMP_FLAG} --branch main \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
--token "${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
- name: "Read published version"
|
- name: "Read published version"
|
||||||
id: version
|
id: version
|
||||||
@@ -269,8 +317,8 @@ jobs:
|
|||||||
!startsWith(steps.platform.outputs.platform, 'joomla')
|
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
SEMVER_TAG="v${VERSION}"
|
SEMVER_TAG="v${VERSION}"
|
||||||
|
|
||||||
echo "Creating semver tag: ${SEMVER_TAG}"
|
echo "Creating semver tag: ${SEMVER_TAG}"
|
||||||
@@ -294,8 +342,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Update release notes and promote changelog
|
- name: Update release notes and promote changelog
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
# Get the stable release info (version and ID)
|
# Get the stable release info (version and ID)
|
||||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
@@ -339,13 +387,47 @@ jobs:
|
|||||||
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
|
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
|
||||||
DATE=$(date +%Y-%m-%d)
|
DATE=$(date +%Y-%m-%d)
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import sys
|
import sys, re
|
||||||
version, date = sys.argv[1], sys.argv[2]
|
version, date = sys.argv[1], sys.argv[2]
|
||||||
content = open('CHANGELOG.md').read()
|
lines = open('CHANGELOG.md').read().split('\n')
|
||||||
old = '## [Unreleased]'
|
h2 = re.compile(r'^##\s+\[([^\]]+)\]')
|
||||||
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
|
header, sections, cur = [], [], None
|
||||||
content = content.replace(old, new, 1)
|
for ln in lines:
|
||||||
open('CHANGELOG.md', 'w').write(content)
|
m = h2.match(ln)
|
||||||
|
if m:
|
||||||
|
if cur: sections.append(cur)
|
||||||
|
cur = {'label': m.group(1).strip(), 'head': ln, 'body': []}
|
||||||
|
elif cur is None:
|
||||||
|
header.append(ln)
|
||||||
|
else:
|
||||||
|
cur['body'].append(ln)
|
||||||
|
if cur: sections.append(cur)
|
||||||
|
def nonempty(b): return any(x.strip() for x in b)
|
||||||
|
def trim(b):
|
||||||
|
b = b[:]
|
||||||
|
while b and not b[0].strip(): b.pop(0)
|
||||||
|
while b and not b[-1].strip(): b.pop()
|
||||||
|
return b
|
||||||
|
unreleased, order, bykey = [], [], {}
|
||||||
|
for s in sections:
|
||||||
|
key = s['label'].lower()
|
||||||
|
if key == 'unreleased':
|
||||||
|
if nonempty(s['body']): unreleased += s['body']
|
||||||
|
continue
|
||||||
|
if not nonempty(s['body']): continue # drop blank release sections
|
||||||
|
if key in bykey: bykey[key]['body'] += [''] + s['body'] # merge duplicate heading (never drop content)
|
||||||
|
else: bykey[key] = s; order.append(key)
|
||||||
|
rest, seen = [bykey[k] for k in order], set(order)
|
||||||
|
out = []
|
||||||
|
htxt = '\n'.join(header).rstrip()
|
||||||
|
if htxt: out += [htxt, '']
|
||||||
|
out += ['## [Unreleased]', '']
|
||||||
|
promote = bool(unreleased) and version.lower() not in seen
|
||||||
|
if unreleased and not promote: out += trim(unreleased) + ['']
|
||||||
|
if promote: out += ['## [%s] --- %s' % (version, date), ''] + trim(unreleased) + ['']
|
||||||
|
for s in rest: out += [s['head'], ''] + trim(s['body']) + ['']
|
||||||
|
res = re.sub(r'\n{3,}', '\n\n', '\n'.join(out)).rstrip('\n') + '\n'
|
||||||
|
open('CHANGELOG.md', 'w').write(res)
|
||||||
" "$VERSION" "$DATE"
|
" "$VERSION" "$DATE"
|
||||||
git add CHANGELOG.md
|
git add CHANGELOG.md
|
||||||
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
|
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
|
||||||
@@ -363,10 +445,10 @@ jobs:
|
|||||||
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 }}"
|
||||||
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}"
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
php ${MOKO_CLI}/release_mirror.php \
|
php ${MOKO_CLI}/release_mirror.php \
|
||||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
||||||
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||||
--branch main 2>&1 || true
|
--branch main 2>&1 || true
|
||||||
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -388,36 +470,28 @@ jobs:
|
|||||||
&& echo "main branch pushed to GitHub mirror" \
|
&& echo "main branch pushed to GitHub mirror" \
|
||||||
|| echo "WARNING: GitHub mirror push failed"
|
|| echo "WARNING: GitHub mirror push failed"
|
||||||
|
|
||||||
- name: "Step 11: Delete rc branch and recreate dev from main"
|
- name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)"
|
||||||
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="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
# Delete rc branch (ephemeral — created by promote-rc)
|
# Delete rc branch (ephemeral — created by promote-rc)
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
"${API_BASE}/branches/rc" 2>/dev/null \
|
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||||
&& echo "Deleted rc branch" || echo "rc branch not found"
|
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||||
|
|
||||||
# Delete dev branch
|
# dev is reset from main by the dedicated "Cascade Main -> Dev" workflow
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
# (cascade-dev.yml), which runs after this release completes.
|
||||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
|
||||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/branches" \
|
|
||||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
|
||||||
|
|
||||||
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
- name: "Step 12: Create version branch from main"
|
- name: "Step 12: Create version 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="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
BRANCH_NAME="version/${VERSION}"
|
BRANCH_NAME="version/${VERSION}"
|
||||||
MAIN_SHA=$(git rev-parse HEAD)
|
MAIN_SHA=$(git rev-parse HEAD)
|
||||||
@@ -437,9 +511,9 @@ jobs:
|
|||||||
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="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
php ${MOKO_CLI}/version_reset_dev.php \
|
php ${MOKO_CLI}/version_reset_dev.php \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "${API_BASE}" \
|
||||||
--branch dev --path . 2>&1 || true
|
--branch dev --path . 2>&1 || true
|
||||||
|
|
||||||
# -- Summary --------------------------------------------------------------
|
# -- Summary --------------------------------------------------------------
|
||||||
@@ -463,5 +537,5 @@ jobs:
|
|||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Release | [View](${MOKOGIT_URL}/${GIT_ORG}/${GIT_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.Universal
|
# INGROUP: MokoCLI.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
# PATH: /.mokogit/workflows/branch-cleanup.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Delete feature branches after PR merge
|
# BRIEF: Delete feature branches after PR merge
|
||||||
|
|
||||||
@@ -33,10 +33,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
# URL-encode the branch name's slashes (no PHP dependency on the runner)
|
||||||
|
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
|
||||||
|
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
|
||||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ "$STATUS" = "204" ]; then
|
if [ "$STATUS" = "204" ]; then
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.Cascade
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
|
# PATH: /.mokogit/workflows/cascade-dev.yml
|
||||||
|
# VERSION: 02.01.00
|
||||||
|
# BRIEF: Cascade main -> dev; auto-merge clean, auto-resolve VERSION-stamp-only conflicts, else notify
|
||||||
|
|
||||||
|
name: "Cascade Main -> Dev"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
# Daily safety net: catches drift even when main only received [skip ci] pushes
|
||||||
|
# (which never fire the push trigger above). Off-round minute to avoid a fleet-wide spike.
|
||||||
|
schedule:
|
||||||
|
- cron: '23 7 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
# ntfy destination is configured via repo or org variables (org vars are inherited).
|
||||||
|
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||||
|
NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'git-releases' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cascade:
|
||||||
|
name: Cascade main -> dev
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout (full history for merge/resolve)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
|
||||||
|
- name: Cascade main -> dev (auto-resolve version stamps, else notify)
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
|
||||||
|
AUTH="Authorization: token ${TOKEN}"
|
||||||
|
jqget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
||||||
|
|
||||||
|
# 0. dev must exist
|
||||||
|
if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then
|
||||||
|
echo "No dev branch - nothing to cascade."; exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. is main ahead of dev?
|
||||||
|
AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
|
||||||
|
| python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
|
||||||
|
if [ "${AHEAD:-0}" -eq 0 ]; then
|
||||||
|
echo "dev already up to date with main."; exit 0
|
||||||
|
fi
|
||||||
|
echo "main is ${AHEAD} commit(s) ahead of dev."
|
||||||
|
|
||||||
|
# 2. reuse an open main->dev PR, else create one
|
||||||
|
PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \
|
||||||
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
|
||||||
|
if [ -z "$PR" ]; then
|
||||||
|
RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
|
||||||
|
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges when conflict-free, auto-resolves VERSION-stamp-only conflicts, otherwise left open for manual resolution."}')
|
||||||
|
PR=$(printf '%s' "$RESP" | jqget number)
|
||||||
|
if [ -z "$PR" ]; then
|
||||||
|
echo "::warning::Could not open cascade PR: $RESP"; exit 0
|
||||||
|
fi
|
||||||
|
echo "Opened cascade PR #${PR}"
|
||||||
|
else
|
||||||
|
echo "Reusing open cascade PR #${PR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
curl -sS \
|
||||||
|
-H "Title: ${REPO}: dev cascade needs manual merge" \
|
||||||
|
-H "Tags: warning,twisted_rightwards_arrows" \
|
||||||
|
-H "Priority: high" \
|
||||||
|
-H "Click: ${MOKOGIT_URL}/${REPO}/pulls/${PR}" \
|
||||||
|
-d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \
|
||||||
|
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. wait for MokoGIT to compute mergeability (conflict detection)
|
||||||
|
MERGEABLE=""
|
||||||
|
for _ in 1 2 3 4 5 6; do
|
||||||
|
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqget mergeable)
|
||||||
|
case "$MERGEABLE" in True|False) break ;; esac
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
echo "mergeable=${MERGEABLE}"
|
||||||
|
|
||||||
|
# 4a. conflict-free -> merge via API (existing behaviour)
|
||||||
|
if [ "$MERGEABLE" = "True" ]; then
|
||||||
|
CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
|
||||||
|
-X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
|
||||||
|
if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
|
||||||
|
echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
|
||||||
|
notify "could not be auto-merged (HTTP ${CODE})."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4b. conflicts -> try to auto-resolve if they are ONLY VERSION-stamp lines.
|
||||||
|
echo "PR not cleanly mergeable; checking whether conflicts are VERSION-stamp-only..."
|
||||||
|
git config user.name "MokoGIT Cascade"
|
||||||
|
git config user.email "actions@mokoconsulting.tech"
|
||||||
|
git fetch --quiet origin main dev
|
||||||
|
git checkout -B dev origin/dev
|
||||||
|
|
||||||
|
if git merge --no-ff --no-commit origin/main >/dev/null 2>&1; then
|
||||||
|
# Became clean at git level (e.g. mergeability was still computing) -> commit + push.
|
||||||
|
git commit -m "chore(sync): cascade main -> dev [skip ci]" >/dev/null
|
||||||
|
git push origin dev
|
||||||
|
echo "Cascade merged cleanly at git level and pushed to dev."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONFLICTS=$(git diff --name-only --diff-filter=U)
|
||||||
|
echo "Conflicted files:"; echo "${CONFLICTS}"
|
||||||
|
|
||||||
|
# A conflict is "stamp-only" when every line inside every conflict block matches
|
||||||
|
# a version-stamp pattern (VERSION: header, <version> element, or CHANGELOG title).
|
||||||
|
is_stamp_only() {
|
||||||
|
awk '
|
||||||
|
/^<<<<<<< / { inc=1; next }
|
||||||
|
inc && /^=======$/ { next }
|
||||||
|
/^>>>>>>> / { inc=0; next }
|
||||||
|
inc { if ($0 !~ /(VERSION:|<version>|# Changelog)/) { bad=1 } }
|
||||||
|
END { exit(bad ? 1 : 0) }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
# Resolve a stamp-only file by keeping dev (ours) for the conflicting lines,
|
||||||
|
# preserving all auto-merged content around them.
|
||||||
|
keep_ours() {
|
||||||
|
awk '
|
||||||
|
/^<<<<<<< / { inc=1; side="ours"; next }
|
||||||
|
inc && /^=======$/ { side="theirs"; next }
|
||||||
|
/^>>>>>>> / { inc=0; next }
|
||||||
|
{ if (!inc) { print; next } if (side=="ours") print }
|
||||||
|
' "$1" > "$1.resolved" && mv "$1.resolved" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
ALL_STAMP=1
|
||||||
|
for f in ${CONFLICTS}; do
|
||||||
|
if ! is_stamp_only "$f"; then
|
||||||
|
echo "::notice::$f has non-stamp conflicts -> manual resolution required."
|
||||||
|
ALL_STAMP=0; break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$ALL_STAMP" != "1" ]; then
|
||||||
|
git merge --abort || true
|
||||||
|
notify "has non-version-stamp conflicts and cannot be auto-resolved."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All conflicts are VERSION-stamp-only; resolving in favour of dev."
|
||||||
|
for f in ${CONFLICTS}; do
|
||||||
|
keep_ours "$f"
|
||||||
|
git add "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Best-effort: normalise stamps to dev's version if mokocli is available.
|
||||||
|
if [ -f /opt/mokocli/cli/version_check.php ]; then
|
||||||
|
php /opt/mokocli/cli/version_check.php --fix || true
|
||||||
|
git add -A
|
||||||
|
fi
|
||||||
|
|
||||||
|
git commit -m "chore(sync): cascade main -> dev (auto-resolved version stamps) [skip ci]" >/dev/null
|
||||||
|
git push origin dev
|
||||||
|
echo "Cascade auto-resolved and pushed to dev."
|
||||||
|
|
||||||
|
# Close the now-redundant PR (its changes are in dev) with an explanatory comment.
|
||||||
|
curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
|
||||||
|
-d '{"body":"Auto-resolved VERSION-stamp-only conflicts and pushed the merge to dev. Closing."}' >/dev/null || true
|
||||||
|
curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
|
||||||
|
-d '{"state":"closed"}' >/dev/null || true
|
||||||
@@ -3,16 +3,22 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.CI
|
# INGROUP: MokoCLI.CI
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /.gitea/workflows/ci-generic.yml
|
# PATH: /.mokogit/workflows/ci-generic.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
|
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
|
||||||
|
|
||||||
name: "Generic: Project CI"
|
name: "Generic: Project CI"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -26,6 +32,8 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
name: Lint & Validate
|
name: Lint & Validate
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -123,7 +131,11 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
name: Tests
|
name: Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: lint
|
# Independent job (no `needs: lint`): the MokoGIT Actions scheduler does not
|
||||||
|
# offer the dependent 2nd job of a needs-chain to runners, so it stalls in
|
||||||
|
# "waiting" and is reaped by ABANDONED_JOB_TIMEOUT. Guard template repos
|
||||||
|
# directly (same condition lint uses) instead of gating on lint's result.
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
|
# PATH: /.mokogit/workflows/ci-issue-reporter.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Reusable workflow — creates/updates a MokoGIT issue when a CI gate fails.
|
||||||
|
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
||||||
|
|
||||||
|
name: "Universal: CI Issue Reporter"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
gate:
|
||||||
|
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
details:
|
||||||
|
description: "Human-readable failure description"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
severity:
|
||||||
|
description: "error or warning"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: "error"
|
||||||
|
workflow:
|
||||||
|
description: "Workflow name for the issue title"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
secrets:
|
||||||
|
MOKOGIT_TOKEN:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
report:
|
||||||
|
name: "Report: ${{ inputs.gate }}"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone MokoCLI
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
MOKOGIT_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||||
|
git clone --depth 1 --filter=blob:none --sparse "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||||
|
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
||||||
|
|
||||||
|
- name: Report CI failure
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
run: |
|
||||||
|
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
||||||
|
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
||||||
|
--gate "${{ inputs.gate }}" \
|
||||||
|
--details "${{ inputs.details }}" \
|
||||||
|
--severity "${{ inputs.severity }}" \
|
||||||
|
--workflow "${{ inputs.workflow }}"
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.Maintenance
|
# INGROUP: MokoCLI.Maintenance
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/cleanup.yml
|
# PATH: /.mokogit/workflows/cleanup.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -33,30 +33,30 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
|
||||||
- name: Delete merged branches
|
- name: Delete merged branches
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Merged Branch Cleanup ==="
|
echo "=== Merged Branch Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGIT_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 ${MOKOGIT_TOKEN}" \
|
||||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
for BRANCH in $BRANCHES; do
|
for BRANCH in $BRANCHES; do
|
||||||
# Skip protected branches
|
# Skip protected branches
|
||||||
case "$BRANCH" in
|
case "$BRANCH" in
|
||||||
main|master|develop|release/*|hotfix/*) continue ;;
|
main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# 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 ${MOKOGIT_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 }}
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Workflow Run Cleanup ==="
|
echo "=== Workflow Run Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGIT_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 ${MOKOGIT_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 ${MOKOGIT_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
|
||||||
+2
-2
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: Checkout MokoCLI
|
- name: Checkout MokoCLI
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
run: |
|
run: |
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
DRY_RUN="--dry-run"
|
DRY_RUN="--dry-run"
|
||||||
fi
|
fi
|
||||||
php cli/branch_protect_org.php \
|
php cli/branch_protect_org.php \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" \
|
||||||
--org "MokoConsulting" \
|
--org "MokoConsulting" \
|
||||||
$DRY_RUN
|
$DRY_RUN
|
||||||
|
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Git.Workflow
|
||||||
# INGROUP: mokocli.CI
|
# INGROUP: mokocli.CI
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.mokogitea/workflows/ci-platform.yml
|
# PATH: /.mokogit/workflows/ci-platform.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: mokocli CI — the standards engine validates itself
|
# BRIEF: mokocli CI — the standards engine validates itself
|
||||||
#
|
#
|
||||||
@@ -41,7 +41,7 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- 'wiki/**'
|
- 'wiki/**'
|
||||||
- '.mokogitea/ISSUE_TEMPLATE/**'
|
- '.mokogit/ISSUE_TEMPLATE/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
|
|
||||||
- name: "PHPCS (PSR-12)"
|
- name: "PHPCS (PSR-12)"
|
||||||
run: |
|
run: |
|
||||||
vendor/bin/phpcs --standard=phpcs.xml --report=summary --warning-severity=0 lib/ validate/ automation/ 2>&1 || {
|
vendor/bin/phpcs --standard=phpcs.xml --report=summary --warning-severity=0 lib/ validate/ 2>&1 || {
|
||||||
echo "::error::PHPCS found coding standard violations"
|
echo "::error::PHPCS found coding standard violations"
|
||||||
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Coding standard violations detected. Run \`composer phpcs\` locally." >> $GITHUB_STEP_SUMMARY
|
echo "Coding standard violations detected. Run \`composer phpcs\` locally." >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -356,7 +356,7 @@ jobs:
|
|||||||
echo "::error file=${file}::Invalid YAML"
|
echo "::error file=${file}::Invalid YAML"
|
||||||
ERRORS=$((ERRORS + 1))
|
ERRORS=$((ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
done < <(find .mokogitea/workflows/ -name "*.yml" -o -name "*.yaml" 2>/dev/null | tr '\n' '\0')
|
done < <(find .mokogit/workflows/ -name "*.yml" -o -name "*.yaml" 2>/dev/null | tr '\n' '\0')
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "### Template Integrity"
|
echo "### Template Integrity"
|
||||||
@@ -453,8 +453,8 @@ jobs:
|
|||||||
needs.governance.result == 'failure' ||
|
needs.governance.result == 'failure' ||
|
||||||
needs.templates.result == 'failure')
|
needs.templates.result == 'failure')
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
run: |
|
run: |
|
||||||
chmod +x automation/ci-issue-reporter.sh
|
chmod +x automation/ci-issue-reporter.sh
|
||||||
REPORTER="./automation/ci-issue-reporter.sh"
|
REPORTER="./automation/ci-issue-reporter.sh"
|
||||||
+6
-6
@@ -13,7 +13,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
@@ -43,13 +43,13 @@ jobs:
|
|||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "Package version: ${VERSION}"
|
echo "Package version: ${VERSION}"
|
||||||
|
|
||||||
# Gitea Composer Registry — auto-publishes from tags
|
# Git Composer Registry — auto-publishes from tags
|
||||||
# The tag push itself registers the package at:
|
# The tag push itself registers the package at:
|
||||||
# https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer
|
# https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer
|
||||||
- name: Verify Gitea registry
|
- name: Verify Git registry
|
||||||
run: |
|
run: |
|
||||||
echo "Gitea Composer registry auto-publishes from tags."
|
echo "Git Composer registry auto-publishes from tags."
|
||||||
echo "Package available at: ${GITEA_URL}/api/packages/MokoConsulting/composer"
|
echo "Package available at: ${MOKOGIT_URL}/api/packages/MokoConsulting/composer"
|
||||||
echo "Install: composer require mokoconsulting/mokocli"
|
echo "Install: composer require mokoconsulting/mokocli"
|
||||||
|
|
||||||
# Packagist — notify of new version (points to GitHub mirror which Packagist can access)
|
# Packagist — notify of new version (points to GitHub mirror which Packagist can access)
|
||||||
@@ -72,5 +72,5 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Registry | Status |" >> $GITHUB_STEP_SUMMARY
|
echo "| Registry | Status |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
|
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Gitea | \`composer require mokoconsulting/mokocli:${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Git | \`composer require mokoconsulting/mokocli:${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Packagist | \`composer require mokoconsulting/mokocli\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Packagist | \`composer require mokoconsulting/mokocli\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
+7
-7
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Git.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.mokogitea/workflows/sync-feature-versions.yml
|
# PATH: /.mokogit/workflows/sync-feature-versions.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Merge dev into open feature branches after version bumps
|
# BRIEF: Merge dev into open feature branches after version bumps
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
@@ -38,13 +38,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: dev
|
ref: dev
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
|
||||||
- name: Configure git
|
- name: Configure git
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "git-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "git-actions[bot]"
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
- name: Merge dev into feature branches
|
- name: Merge dev into feature branches
|
||||||
run: |
|
run: |
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.Security
|
# INGROUP: MokoCLI.Security
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /templates/workflows/gitleaks.yml.template
|
# PATH: /.mokogit/workflows/gitleaks.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||||
#
|
#
|
||||||
@@ -34,7 +34,7 @@ permissions:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-security' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
gitleaks:
|
gitleaks:
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: MokoCLI.Automation
|
||||||
# VERSION: 09.40.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
@@ -19,7 +19,7 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-branch:
|
create-branch:
|
||||||
@@ -28,8 +28,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Create branch and comment
|
- name: Create branch and comment
|
||||||
run: |
|
run: |
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGIT_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 }}"
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
echo "Created branch: ${BRANCH}"
|
echo "Created branch: ${BRANCH}"
|
||||||
|
|
||||||
# Comment on issue with branch link
|
# Comment on issue with branch link
|
||||||
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
REPO_URL="${MOKOGIT_URL}/${{ github.repository }}"
|
||||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||||
|
|
||||||
curl -sf -X POST \
|
curl -sf -X POST \
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.Notifications
|
# INGROUP: MokoCLI.Notifications
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/notify.yml
|
# PATH: /.mokogit/workflows/notify.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
||||||
|
|
||||||
@@ -15,9 +15,9 @@ name: "Universal: Notifications"
|
|||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows:
|
workflows:
|
||||||
- "Joomla Build & Release"
|
- "Universal: Build & Release"
|
||||||
- "Joomla Extension CI"
|
- "Joomla: Extension CI"
|
||||||
- "Deploy"
|
- "Generic: Project CI"
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ permissions:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }}
|
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-releases' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify:
|
notify:
|
||||||
@@ -46,13 +46,13 @@ jobs:
|
|||||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||||
URL="${{ github.event.workflow_run.html_url }}"
|
URL="${{ github.event.workflow_run.html_url }}"
|
||||||
|
|
||||||
curl -sS \
|
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
|
||||||
-H "Title: ${REPO} released" \
|
-H "Title: ${REPO} released" \
|
||||||
-H "Tags: white_check_mark,package" \
|
-H "Tags: white_check_mark,package" \
|
||||||
-H "Priority: default" \
|
-H "Priority: default" \
|
||||||
-H "Click: ${URL}" \
|
-H "Click: ${URL}" \
|
||||||
-d "${WORKFLOW} completed successfully." \
|
-d "${WORKFLOW} completed successfully." \
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
|
||||||
|
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: github.event.workflow_run.conclusion == 'failure'
|
if: github.event.workflow_run.conclusion == 'failure'
|
||||||
@@ -61,10 +61,10 @@ jobs:
|
|||||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||||
URL="${{ github.event.workflow_run.html_url }}"
|
URL="${{ github.event.workflow_run.html_url }}"
|
||||||
|
|
||||||
curl -sS \
|
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
|
||||||
-H "Title: ${REPO} workflow failed" \
|
-H "Title: ${REPO} workflow failed" \
|
||||||
-H "Tags: x,warning" \
|
-H "Tags: x,warning" \
|
||||||
-H "Priority: high" \
|
-H "Priority: high" \
|
||||||
-H "Click: ${URL}" \
|
-H "Click: ${URL}" \
|
||||||
-d "${WORKFLOW} failed. Check the run for details." \
|
-d "${WORKFLOW} failed. Check the run for details." \
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: moko-platform.CI
|
# INGROUP: MokoCLI.CI
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /templates/workflows/universal/pr-check.yml.template
|
# PATH: /.mokogit/workflows/pr-check.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: PR gate — branch policy + code validation before merge
|
# BRIEF: PR gate — branch policy + code validation before merge
|
||||||
|
|
||||||
@@ -47,15 +47,15 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
fix/*|bugfix/*)
|
fix/*|bugfix/*)
|
||||||
if [ "$BASE" != "dev" ]; then
|
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||||
ALLOWED=false
|
ALLOWED=false
|
||||||
REASON="Fix branches must target 'dev', not '${BASE}'"
|
REASON="Fix branches must target 'dev' or 'main', not '${BASE}'"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
patch/*)
|
patch/*)
|
||||||
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
|
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ] && [ "$BASE" != "main" ]; then
|
||||||
ALLOWED=false
|
ALLOWED=false
|
||||||
REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
|
REASON="Patch branches must target 'dev', 'rc', or 'main', not '${BASE}'"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
hotfix/*)
|
hotfix/*)
|
||||||
@@ -86,7 +86,8 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
echo "- \`fix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`patch/*\` → \`dev\`, \`rc\`, or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -96,6 +97,80 @@ jobs:
|
|||||||
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||||
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# ── Docs Update Gate (main PRs) ─────────────────────────────────────────
|
||||||
|
require-docs:
|
||||||
|
name: Require Docs Update
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Enforce only on PRs merging into main: README.md + CHANGELOG.md must both be updated.
|
||||||
|
if: ${{ github.base_ref == 'main' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Require README.md and CHANGELOG.md in the PR diff
|
||||||
|
run: |
|
||||||
|
BASE="${{ github.event.pull_request.base.sha }}"
|
||||||
|
HEAD="${{ github.event.pull_request.head.sha }}"
|
||||||
|
CHANGED="$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || true)"
|
||||||
|
if [ -z "$CHANGED" ]; then
|
||||||
|
git fetch -q origin "${{ github.base_ref }}" 2>/dev/null || true
|
||||||
|
CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
echo "Changed files in PR:"
|
||||||
|
echo "$CHANGED"
|
||||||
|
MISSING=""
|
||||||
|
echo "$CHANGED" | grep -qxE 'README\.md' || MISSING="README.md"
|
||||||
|
echo "$CHANGED" | grep -qxE 'CHANGELOG\.md' || MISSING="${MISSING:+$MISSING, }CHANGELOG.md"
|
||||||
|
if [ -n "$MISSING" ]; then
|
||||||
|
echo "::error::PRs into main must update: ${MISSING}"
|
||||||
|
{
|
||||||
|
echo "## Docs Update Required"
|
||||||
|
echo ""
|
||||||
|
echo "PRs merging into \`main\` must update both **README.md** and **CHANGELOG.md**."
|
||||||
|
echo ""
|
||||||
|
echo "Not updated in this PR: **${MISSING}**"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Docs update present (README.md + CHANGELOG.md)"
|
||||||
|
echo "## Docs Update: Passed" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
# ── Wiki Update Reminder (main PRs, non-blocking) ───────────────────────
|
||||||
|
wiki-reminder:
|
||||||
|
name: Wiki Update Reminder
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.base_ref == 'main' }}
|
||||||
|
steps:
|
||||||
|
- name: Remind to update the wiki
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
PR: ${{ github.event.pull_request.number }}
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
{
|
||||||
|
echo "## Wiki Update Reminder"
|
||||||
|
echo ""
|
||||||
|
echo "Docs are **wiki-first** at MokoConsulting. If this change affects behavior, usage, configuration, or standards, update the repo wiki:"
|
||||||
|
echo ""
|
||||||
|
echo "- ${SERVER}/${REPO}/wiki"
|
||||||
|
echo ""
|
||||||
|
echo "_Non-blocking reminder._"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
# Post a single PR comment (idempotent via hidden marker); best-effort, never fails.
|
||||||
|
API="${SERVER}/api/v1/repos/${REPO}/issues/${PR}/comments"
|
||||||
|
if [ -n "${TOKEN:-}" ] && [ -n "${PR:-}" ]; then
|
||||||
|
existing="$(curl -sf -H "Authorization: token ${TOKEN}" "$API" 2>/dev/null | grep -c 'wiki-reminder' || true)"
|
||||||
|
if [ "${existing:-0}" -eq 0 ]; then
|
||||||
|
curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -X POST "$API" \
|
||||||
|
-d '{"body":"<!-- wiki-reminder -->\n\n**Wiki reminder:** docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. _(non-blocking)_"}' >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "Wiki reminder emitted (non-blocking)."
|
||||||
|
|
||||||
# ── Secret Scanning ──────────────────────────────────────────────────
|
# ── Secret Scanning ──────────────────────────────────────────────────
|
||||||
gitleaks:
|
gitleaks:
|
||||||
name: Secret Scan
|
name: Secret Scan
|
||||||
@@ -126,6 +201,8 @@ jobs:
|
|||||||
validate:
|
validate:
|
||||||
name: Validate PR
|
name: Validate PR
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# Skip on template repos (Template-*) — no real manifest/source/changelog to validate.
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -133,7 +210,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for merge conflict markers
|
- name: Check for merge conflict markers
|
||||||
run: |
|
run: |
|
||||||
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --exclude-dir='.git' --exclude-dir='.mokogit' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
||||||
if [ -n "$CONFLICTS" ]; then
|
if [ -n "$CONFLICTS" ]; then
|
||||||
echo "::error::Merge conflict markers found in source files"
|
echo "::error::Merge conflict markers found in source files"
|
||||||
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
|
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -147,11 +224,12 @@ jobs:
|
|||||||
- name: Detect platform
|
- name: Detect platform
|
||||||
id: platform
|
id: platform
|
||||||
run: |
|
run: |
|
||||||
# Read platform from XML manifest (<platform> tag) or plain text fallback
|
# Platform comes from the MokoGIT metadata API (public GET).
|
||||||
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
|
API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata"
|
||||||
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
|
PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)"
|
||||||
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
||||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Detected platform: $PLATFORM"
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||||
@@ -180,8 +258,9 @@ jobs:
|
|||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
# Skip vendor, node_modules, and index.html stub files
|
# Skip vendor, node_modules, and index.html stub files
|
||||||
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
|
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
|
||||||
# Check first 10 lines for JEXEC or JPATH guard
|
# Scan the whole file for the JEXEC/JPATH guard: it is placed after
|
||||||
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
|
# the SPDX/file-header docblock, which commonly runs past 20 lines.
|
||||||
|
if ! grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]" "$file"; then
|
||||||
echo "::error file=${file}::Missing JEXEC guard: ${file}"
|
echo "::error file=${file}::Missing JEXEC guard: ${file}"
|
||||||
ERRORS=$((ERRORS + 1))
|
ERRORS=$((ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
@@ -272,7 +351,7 @@ jobs:
|
|||||||
joomla)
|
joomla)
|
||||||
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
if [ -z "$MANIFEST" ]; then
|
if [ -z "$MANIFEST" ]; then
|
||||||
echo "::warning::No Joomla manifest found (WaaS site)"
|
echo "::warning::No Joomla manifest found (MokoSuite site)"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
echo "Manifest: ${MANIFEST}"
|
echo "Manifest: ${MANIFEST}"
|
||||||
@@ -282,10 +361,10 @@ jobs:
|
|||||||
for ELEMENT in name version description; do
|
for ELEMENT in name version description; do
|
||||||
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
||||||
done
|
done
|
||||||
# Block legacy raw/branch update server URLs on MokoGitea
|
# Block legacy raw/branch update server URLs on MokoGIT
|
||||||
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
|
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogit\|git\.mokoconsulting\.tech' || true)
|
||||||
if [ -n "$RAW_URLS" ]; then
|
if [ -n "$RAW_URLS" ]; then
|
||||||
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
|
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGIT. Use the MokoGIT Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
|
||||||
echo "$RAW_URLS"
|
echo "$RAW_URLS"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -492,43 +571,33 @@ jobs:
|
|||||||
name: Build RC Package
|
name: Build RC Package
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [branch-policy, validate]
|
needs: [branch-policy, validate]
|
||||||
|
# Run only when both gates succeeded; always() forces evaluation so a skipped
|
||||||
|
# validate (e.g. template repos) skips this job cleanly instead of hanging.
|
||||||
|
if: ${{ always() && needs.branch-policy.result == 'success' && needs.validate.result == 'success' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger RC pre-release
|
- name: Trigger RC pre-release
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
BRANCH: ${{ github.head_ref }}
|
BRANCH: ${{ github.head_ref }}
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_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 ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
curl -s -X POST "${MOKOGIT_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGIT_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
|
||||||
|
|
||||||
# ── Issue Reporter ──────────────────────────────────────────────────────
|
# ── Issue Reporter ──────────────────────────────────────────────────────
|
||||||
report-issues:
|
report-issues:
|
||||||
name: Report Issues
|
name: Report Issues
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [branch-policy, validate]
|
needs: [branch-policy, validate]
|
||||||
if: >-
|
if: >-
|
||||||
always() &&
|
always() &&
|
||||||
needs.validate.result == 'failure'
|
needs.validate.result == 'failure'
|
||||||
|
uses: ./.mokogit/workflows/ci-issue-reporter.yml
|
||||||
steps:
|
with:
|
||||||
- name: Checkout
|
gate: "PR Validation"
|
||||||
uses: actions/checkout@v4
|
workflow: "PR Check"
|
||||||
with:
|
severity: error
|
||||||
sparse-checkout: automation/ci-issue-reporter.sh
|
details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
||||||
sparse-checkout-cone-mode: false
|
secrets: inherit
|
||||||
|
|
||||||
- name: "File issue for PR validation failure"
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
run: |
|
|
||||||
chmod +x automation/ci-issue-reporter.sh
|
|
||||||
./automation/ci-issue-reporter.sh \
|
|
||||||
--gate "PR Validation" \
|
|
||||||
--workflow "PR Check" \
|
|
||||||
--severity error \
|
|
||||||
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: mokocli.Release
|
# INGROUP: MokoCLI.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
# PATH: /.mokogit/workflows/pre-release.yml
|
||||||
# VERSION: 05.01.00
|
# VERSION: 05.02.01
|
||||||
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
||||||
|
|
||||||
name: "Universal: Pre-Release"
|
name: "Universal: Pre-Release"
|
||||||
@@ -40,29 +40,38 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
||||||
runs-on: release
|
runs-on: release
|
||||||
|
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||||
if: >-
|
if: >-
|
||||||
github.event_name == 'workflow_dispatch' ||
|
!startsWith(github.event.repository.name, 'Template-') &&
|
||||||
github.event_name == 'push'
|
(
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
github.event_name == 'push'
|
||||||
|
)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
ref: ${{ github.ref_name }}
|
ref: ${{ github.ref_name }}
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Update submodules to main
|
||||||
|
run: |
|
||||||
|
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Setup MokoCLI tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
|
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
|
||||||
@@ -147,24 +156,30 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Commit version bump
|
# Commit version bump
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "mokogit-actions[bot]"
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_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
|
# Push the bump commit, but do NOT fail the release if the target branch
|
||||||
|
# is protected and the release identity is not on the push allowlist.
|
||||||
|
# The build proceeds from the in-tree bumped version regardless; if the
|
||||||
|
# push is rejected, the next run simply re-bumps from the same base.
|
||||||
|
if ! git push origin HEAD 2>&1; then
|
||||||
|
echo "::warning::Version-bump commit could not be pushed (protected branch?). Building from in-tree version ${VERSION} anyway."
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Auto-detect element via manifest_element.php
|
# Auto-detect element via manifest_element.php
|
||||||
php ${MOKO_CLI}/manifest_element.php \
|
php ${MOKO_CLI}/manifest_element.php \
|
||||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||||
--repo "${GITEA_REPO}" --github-output
|
--repo "${GIT_REPO}" --github-output
|
||||||
|
|
||||||
# Read back element outputs
|
# Read back element outputs
|
||||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
ZIP_NAME=$(grep '^zip_name=' "$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 "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
|
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
@@ -182,18 +197,18 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
php ${MOKO_CLI}/release_create.php \
|
php ${MOKO_CLI}/release_create.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
||||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
--repo "${GIT_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||||
|
|
||||||
- name: Update release notes from CHANGELOG.md
|
- name: Update release notes from CHANGELOG.md
|
||||||
if: steps.eligibility.outputs.proceed == 'true'
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
@@ -204,7 +219,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Update release body via API
|
# Update release body via API
|
||||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
|
||||||
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ -n "$RELEASE_ID" ]; then
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
@@ -216,7 +231,7 @@ jobs:
|
|||||||
'${API_BASE}/releases/${RELEASE_ID}',
|
'${API_BASE}/releases/${RELEASE_ID}',
|
||||||
data=payload, method='PATCH',
|
data=payload, method='PATCH',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
|
'Authorization': 'token ${{ secrets.MOKOGIT_TOKEN }}',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})
|
})
|
||||||
urllib.request.urlopen(req)
|
urllib.request.urlopen(req)
|
||||||
@@ -230,21 +245,21 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
php ${MOKO_CLI}/release_package.php \
|
php ${MOKO_CLI}/release_package.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
||||||
--repo "${GITEA_REPO}" --output /tmp || true
|
--repo "${GIT_REPO}" --output /tmp || true
|
||||||
|
|
||||||
# updates.xml is generated dynamically by MokoGitea license server
|
# updates.xml is generated dynamically by MokoGIT license server
|
||||||
# No need to build, commit, or sync updates.xml from workflows
|
# No need to build, commit, or sync updates.xml from workflows
|
||||||
|
|
||||||
- name: "Delete lesser pre-release channels (cascade)"
|
- name: "Delete lesser pre-release channels (cascade)"
|
||||||
if: steps.eligibility.outputs.proceed == 'true'
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
php ${MOKO_CLI}/release_cascade.php \
|
php ${MOKO_CLI}/release_cascade.php \
|
||||||
--stability "${{ steps.meta.outputs.stability }}" \
|
--stability "${{ steps.meta.outputs.stability }}" \
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: mokocli.Universal
|
# INGROUP: MokoCLI.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /.mokogitea/workflows/rc-revert.yml
|
# PATH: /.mokogit/workflows/rc-revert.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
||||||
|
|
||||||
@@ -25,16 +25,25 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: >-
|
if: >-
|
||||||
github.event.pull_request.merged == false &&
|
github.event.pull_request.merged == false &&
|
||||||
startsWith(github.event.pull_request.head.ref, 'rc/')
|
startsWith(github.event.pull_request.head.ref, 'rc/') &&
|
||||||
|
!startsWith(github.event.repository.name, 'Template-')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Rename branch
|
- name: Rename branch
|
||||||
|
env:
|
||||||
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
set -euo pipefail
|
||||||
|
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
||||||
|
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
||||||
|
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
||||||
|
fi
|
||||||
SUFFIX="${BRANCH#rc/}"
|
SUFFIX="${BRANCH#rc/}"
|
||||||
DEV_BRANCH="dev/${SUFFIX}"
|
DEV_BRANCH="dev/${SUFFIX}"
|
||||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
API="${GIT_URL}/api/v1/repos/${REPO}/branches"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
|
||||||
|
|
||||||
# Create dev/ branch from rc/ branch
|
# Create dev/ branch from rc/ branch
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||||
@@ -42,25 +51,22 @@ jobs:
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
||||||
"${API}" 2>/dev/null || true)
|
"${API}" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ "$STATUS" = "201" ]; then
|
if [ "$STATUS" = "201" ]; then
|
||||||
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
else
|
else
|
||||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
|
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Delete rc/ branch
|
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
||||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
-H "Authorization: token ${TOKEN}" \
|
-H "Authorization: token ${TOKEN}" \
|
||||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ "$STATUS" = "204" ]; then
|
if [ "$STATUS" = "204" ]; then
|
||||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
else
|
else
|
||||||
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
|
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: mokocli.Validation
|
# INGROUP: MokoCLI.Validation
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
# PATH: /templates/workflows/joomla/repo_health.yml.template
|
# PATH: /.mokogit/workflows/repo-health.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
|
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -45,7 +45,7 @@ env:
|
|||||||
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
|
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
|
||||||
|
|
||||||
# Repo health policy
|
# Repo health policy
|
||||||
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/
|
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogit/workflows/
|
||||||
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
|
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
|
||||||
REPO_DISALLOWED_DIRS:
|
REPO_DISALLOWED_DIRS:
|
||||||
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
||||||
@@ -56,7 +56,7 @@ env:
|
|||||||
# File / directory variables
|
# File / directory variables
|
||||||
DOCS_INDEX: docs/docs-index.md
|
DOCS_INDEX: docs/docs-index.md
|
||||||
SCRIPT_DIR: scripts
|
SCRIPT_DIR: scripts
|
||||||
WORKFLOWS_DIR: .mokogitea/workflows
|
WORKFLOWS_DIR: .mokogit/workflows
|
||||||
SHELLCHECK_PATTERN: '*.sh'
|
SHELLCHECK_PATTERN: '*.sh'
|
||||||
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
|
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- name: Check actor permission (admin only)
|
- name: Check actor permission (admin only)
|
||||||
id: perm
|
id: perm
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
ACTOR: ${{ github.actor }}
|
ACTOR: ${{ github.actor }}
|
||||||
run: |
|
run: |
|
||||||
@@ -88,14 +88,14 @@ jobs:
|
|||||||
|
|
||||||
# Hardcoded authorized users — always allowed
|
# Hardcoded authorized users — always allowed
|
||||||
case "$ACTOR" in
|
case "$ACTOR" in
|
||||||
jmiller|gitea-actions[bot])
|
jmiller|mokogit-actions[bot])
|
||||||
ALLOWED=true
|
ALLOWED=true
|
||||||
PERMISSION=admin
|
PERMISSION=admin
|
||||||
METHOD="hardcoded allowlist"
|
METHOD="hardcoded allowlist"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Detect platform and check permissions via API
|
# Detect platform and check permissions via API
|
||||||
API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}"
|
API_BASE="${GITHUB_API_URL:-${GIT_API_URL:-https://api.github.com}}"
|
||||||
RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
"${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
|
"${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
|
||||||
PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
|
PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
|
||||||
@@ -605,7 +605,7 @@ jobs:
|
|||||||
printf '%s\n' '| Domain | Status | Notes |'
|
printf '%s\n' '| Domain | Status | Notes |'
|
||||||
printf '%s\n' '|---|---|---|'
|
printf '%s\n' '|---|---|---|'
|
||||||
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
|
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
|
||||||
printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |'
|
printf '%s\n' '| Release policy | N/A | Releases handled by MokoGIT |'
|
||||||
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
|
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
|
||||||
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
|
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
|
||||||
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
|
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
|
||||||
@@ -671,42 +671,30 @@ jobs:
|
|||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
# Issue Reporter — file issues for failed gates
|
# Issue Reporter — file issues for failed gates
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
report-issues:
|
report-scripts:
|
||||||
name: "Report Issues"
|
name: "Report: Scripts Governance"
|
||||||
runs-on: ubuntu-latest
|
needs: [access_check, scripts_governance]
|
||||||
needs: [access_check, scripts_governance, repo_health]
|
|
||||||
if: >-
|
if: >-
|
||||||
always() &&
|
always() &&
|
||||||
(needs.scripts_governance.result == 'failure' ||
|
needs.scripts_governance.result == 'failure'
|
||||||
needs.repo_health.result == 'failure')
|
uses: ./.mokogit/workflows/ci-issue-reporter.yml
|
||||||
|
with:
|
||||||
|
gate: "Scripts Governance"
|
||||||
|
workflow: "Repo Health"
|
||||||
|
severity: error
|
||||||
|
details: "Scripts directory policy violations detected. Review required and allowed directories."
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
steps:
|
report-health:
|
||||||
- name: Checkout
|
name: "Report: Repository Health"
|
||||||
uses: actions/checkout@v4
|
needs: [access_check, repo_health]
|
||||||
with:
|
if: >-
|
||||||
sparse-checkout: automation/ci-issue-reporter.sh
|
always() &&
|
||||||
sparse-checkout-cone-mode: false
|
needs.repo_health.result == 'failure'
|
||||||
|
uses: ./.mokogit/workflows/ci-issue-reporter.yml
|
||||||
- name: "File issues for failed gates"
|
with:
|
||||||
env:
|
gate: "Repository Health"
|
||||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
workflow: "Repo Health"
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
severity: error
|
||||||
run: |
|
details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
|
||||||
chmod +x automation/ci-issue-reporter.sh
|
secrets: inherit
|
||||||
REPORTER="./automation/ci-issue-reporter.sh"
|
|
||||||
WF="Repo Health"
|
|
||||||
|
|
||||||
report_gate() {
|
|
||||||
local gate="$1" result="$2" details="$3"
|
|
||||||
if [ "$result" = "failure" ]; then
|
|
||||||
"$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
report_gate "Scripts Governance" \
|
|
||||||
"${{ needs.scripts_governance.result }}" \
|
|
||||||
"Scripts directory policy violations detected. Review required and allowed directories."
|
|
||||||
|
|
||||||
report_gate "Repository Health" \
|
|
||||||
"${{ needs.repo_health.result }}" \
|
|
||||||
"Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow.Template
|
# DEFGROUP: MokoGIT.Workflow.Template
|
||||||
# INGROUP: MokoStandards.CI
|
# INGROUP: MokoCLI.CI
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
||||||
# PATH: /.mokogitea/workflows/version-set.yml
|
# PATH: /.mokogit/workflows/version-set.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Set or reset the extension version across all version-bearing files
|
# BRIEF: Set or reset the extension version across all version-bearing files
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ jobs:
|
|||||||
set-version:
|
set-version:
|
||||||
name: Set Version to ${{ inputs.version }}
|
name: Set Version to ${{ inputs.version }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Validate version format
|
- name: Validate version format
|
||||||
@@ -48,7 +49,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
|
token: ${{ secrets.MOKOGIT_TOKEN || github.token }}
|
||||||
ref: ${{ inputs.branch || github.ref }}
|
ref: ${{ inputs.branch || github.ref }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# DISABLED — auto-release Step 11 recreates dev from main after every release.
|
|
||||||
# Cascade-dev is redundant and causes version conflicts when both main and dev
|
|
||||||
# have different version numbers in templateDetails.xml / manifest.xml.
|
|
||||||
name: "Cascade Main → Dev (DISABLED)"
|
|
||||||
on: workflow_dispatch
|
|
||||||
jobs:
|
|
||||||
noop:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- run: echo "Cascade disabled — auto-release handles dev recreation"
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: MokoStandards.Deploy
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
|
|
||||||
# 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 MokoStandards 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}/MokoStandards-API.git" \
|
|
||||||
/tmp/mokostandards-api 2>/dev/null || true
|
|
||||||
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
|
||||||
cd /tmp/mokostandards-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/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
|
||||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
|
||||||
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
|
||||||
else
|
|
||||||
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
|
||||||
|
|
||||||
- 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
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: mokocli.Universal
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
|
||||||
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
|
|
||||||
# VERSION: 01.01.00
|
|
||||||
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
|
|
||||||
|
|
||||||
name: "Universal: Workflow Sync Trigger"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync:
|
|
||||||
name: Sync workflows to live repos
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >-
|
|
||||||
github.event.pull_request.merged == true &&
|
|
||||||
!contains(github.event.pull_request.title, '[skip sync]')
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Determine platform from repo name
|
|
||||||
id: platform
|
|
||||||
run: |
|
|
||||||
REPO="${{ github.event.repository.name }}"
|
|
||||||
case "$REPO" in
|
|
||||||
Template-Joomla) PLATFORM="joomla" ;;
|
|
||||||
Template-Dolibarr) PLATFORM="dolibarr" ;;
|
|
||||||
Template-Go) PLATFORM="go" ;;
|
|
||||||
Template-MCP) PLATFORM="mcp" ;;
|
|
||||||
Template-Generic) PLATFORM="" ;;
|
|
||||||
*) PLATFORM="" ;;
|
|
||||||
esac
|
|
||||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Platform: ${PLATFORM:-all}"
|
|
||||||
|
|
||||||
- name: Clone mokocli
|
|
||||||
env:
|
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
run: |
|
|
||||||
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
|
||||||
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
cd /tmp/mokocli
|
|
||||||
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
|
||||||
|
|
||||||
- name: Run workflow sync
|
|
||||||
env:
|
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
run: |
|
|
||||||
ARGS="--token ${MOKOGITEA_TOKEN}"
|
|
||||||
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
|
|
||||||
ARGS="${ARGS} --phase repos"
|
|
||||||
|
|
||||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
||||||
if [ -n "$PLATFORM" ]; then
|
|
||||||
ARGS="${ARGS} --platform-filter ${PLATFORM}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
|
|
||||||
+11
-6
@@ -4,7 +4,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
FILE INFORMATION
|
FILE INFORMATION
|
||||||
DEFGROUP: MokoCli.Root
|
DEFGROUP: MokoCli.Root
|
||||||
INGROUP: MokoCli
|
INGROUP: MokoCli
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
PATH: /CHANGELOG.md
|
PATH: /CHANGELOG.md
|
||||||
BRIEF: Release changelog
|
BRIEF: Release changelog
|
||||||
-->
|
-->
|
||||||
@@ -12,12 +12,17 @@ BRIEF: Release changelog
|
|||||||
# Changelog
|
# Changelog
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [09.40.00] --- 2026-06-25
|
## [09.43.00] --- 2026-07-05
|
||||||
|
|
||||||
## [09.39.00] --- 2026-06-25
|
## [09.43.00] --- 2026-07-05
|
||||||
|
|
||||||
## [09.39.00] --- 2026-06-25
|
## [09.42.00] --- 2026-07-04
|
||||||
|
|
||||||
## [09.39.00] --- 2026-06-23
|
## [09.42.00] --- 2026-07-04
|
||||||
|
|
||||||
## [09.39.00] --- 2026-06-23
|
### Fixed
|
||||||
|
- branch_rename.php: force-update the target branch ref in place instead of delete+recreate, so `moko repo:rename-branch` works when the target (e.g. `rc`) is a protected branch that cannot be deleted — previously the delete silently failed and the recreate returned HTTP 409
|
||||||
|
|
||||||
|
## [09.42.00] --- 2026-07-04
|
||||||
|
|
||||||
|
## [09.42.00] --- 2026-07-04
|
||||||
|
|||||||
+2
-2
@@ -34,7 +34,7 @@ feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge
|
|||||||
7. **Merging to main** triggers the stable release pipeline:
|
7. **Merging to main** triggers the stable release pipeline:
|
||||||
- Minor version bump (e.g., `02.09.xx` → `02.10.00`)
|
- Minor version bump (e.g., `02.09.xx` → `02.10.00`)
|
||||||
- Stability suffix stripped (clean version)
|
- Stability suffix stripped (clean version)
|
||||||
- Gitea release created with ZIP/tar.gz packages
|
- MokoGIT release created with ZIP/tar.gz packages
|
||||||
- `updates.xml` updated (Joomla extensions)
|
- `updates.xml` updated (Joomla extensions)
|
||||||
- `dev` branch recreated from `main`
|
- `dev` branch recreated from `main`
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ This ensures Joomla sites on ANY stability channel see the update (Joomla only s
|
|||||||
|
|
||||||
The version tools update all files containing version stamps:
|
The version tools update all files containing version stamps:
|
||||||
|
|
||||||
- `.mokogitea/manifest.xml` (canonical source)
|
- `.mokogit/manifest.xml` (canonical source)
|
||||||
- Joomla XML manifests (`<version>` tag)
|
- Joomla XML manifests (`<version>` tag)
|
||||||
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
|
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
|
||||||
- `package.json`, `pyproject.toml`
|
- `package.json`, `pyproject.toml`
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
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
|
||||||
FILE INFORMATION
|
FILE INFORMATION
|
||||||
DEFGROUP: MokoPlatform.Root
|
DEFGROUP: MokoCLI.Root
|
||||||
INGROUP: MokoPlatform
|
INGROUP: MokoCLI
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
PATH: /PLUGIN_SCRIPTS.md
|
PATH: /PLUGIN_SCRIPTS.md
|
||||||
BRIEF: Plugin system CLI documentation
|
BRIEF: Plugin system CLI documentation
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
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
|
||||||
FILE INFORMATION
|
FILE INFORMATION
|
||||||
DEFGROUP: MokoPlatform.Root
|
DEFGROUP: MokoCLI.Root
|
||||||
INGROUP: MokoPlatform
|
INGROUP: MokoCLI
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
PATH: /README.md
|
PATH: /README.md
|
||||||
VERSION: 09.40.00
|
VERSION: 09.43.00
|
||||||
BRIEF: Project overview and documentation
|
BRIEF: Project overview and documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ BRIEF: Project overview and documentation
|
|||||||
|
|
||||||
PHP implementation of mokocli — enterprise standards, automation framework, workflow templates, and bulk sync tooling.
|
PHP implementation of mokocli — enterprise standards, automation framework, workflow templates, and bulk sync tooling.
|
||||||
|
|
||||||
> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoCli-API)
|
> **Primary platform**: [MokoGIT — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoCli-API)
|
||||||
> **Backup mirror**: [GitHub](https://github.com/MokoConsulting/MokoCli-API) *(read-only mirror)*
|
> **Backup mirror**: [GitHub](https://github.com/MokoConsulting/MokoCli-API) *(read-only mirror)*
|
||||||
|
|
||||||
## What Lives Here
|
## What Lives Here
|
||||||
@@ -25,7 +25,7 @@ PHP implementation of mokocli — enterprise standards, automation framework, wo
|
|||||||
|-----------|---------|
|
|-----------|---------|
|
||||||
| `lib/Enterprise/` | 38 PHP enterprise library classes (platform adapters, sync, validation, plugins) |
|
| `lib/Enterprise/` | 38 PHP enterprise library classes (platform adapters, sync, validation, plugins) |
|
||||||
| `cli/` | CLI scripts (archive, create, release, sync rulesets, version management) |
|
| `cli/` | CLI scripts (archive, create, release, sync rulesets, version management) |
|
||||||
| `automation/` | Bulk sync, push files, repo cleanup, Gitea migration |
|
| `automation/` | Bulk sync, push files, repo cleanup, MokoGIT migration |
|
||||||
| `validate/` | 18 validation scripts (health, structure, secrets, syntax, drift) |
|
| `validate/` | 18 validation scripts (health, structure, secrets, syntax, drift) |
|
||||||
| `templates/` | **Workflow templates** and config templates synced to governed repos |
|
| `templates/` | **Workflow templates** and config templates synced to governed repos |
|
||||||
| `definitions/` | Repository structure definitions (`.tf` format) |
|
| `definitions/` | Repository structure definitions (`.tf` format) |
|
||||||
@@ -42,7 +42,7 @@ composer require mokoconsulting-tech/enterprise
|
|||||||
|
|
||||||
### Composer Registry
|
### Composer Registry
|
||||||
|
|
||||||
This package is served from Gitea package registry. Add this to your `composer.json`:
|
This package is served from MokoGIT package registry. Add this to your `composer.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -72,11 +72,11 @@ vendor/bin/moko inventory -- --path .
|
|||||||
|
|
||||||
| Variable | Purpose |
|
| Variable | Purpose |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| `GIT_PLATFORM` | `gitea` (default) or `github` |
|
| `GIT_PLATFORM` | `git` (default) or `github` |
|
||||||
| `GA_TOKEN` | Gitea API / Gitea Actions token |
|
| `GA_TOKEN` | MokoGIT API / MokoGIT Actions token |
|
||||||
| `GH_TOKEN` | GitHub API token (for mirror sync) |
|
| `GH_TOKEN` | GitHub API token (for mirror sync) |
|
||||||
| `GITEA_URL` | Gitea instance URL (default: `https://git.mokoconsulting.tech`) |
|
| `GIT_URL` | MokoGIT instance URL (default: `https://git.mokoconsulting.tech`) |
|
||||||
| `GITEA_ORG` | Gitea organization (default: `MokoConsulting`) |
|
| `GIT_ORG` | MokoGIT organization (default: `MokoConsulting`) |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
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
|
||||||
FILE INFORMATION
|
FILE INFORMATION
|
||||||
DEFGROUP: MokoPlatform.Index
|
DEFGROUP: MokoCLI.Index
|
||||||
INGROUP: MokoPlatform.Analysis
|
INGROUP: MokoCLI.Analysis
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
PATH: /analysis/index.md
|
PATH: /analysis/index.md
|
||||||
BRIEF: Analysis directory index
|
BRIEF: Analysis directory index
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
*
|
*
|
||||||
* COMMANDS (run `php bin/moko list` for the full list — 97 commands)
|
* COMMANDS (run `php bin/moko list` for the full list — 97 commands)
|
||||||
*
|
*
|
||||||
* Automation sync, automation:cleanup, automation:migrate-gitea
|
* Automation sync, automation:cleanup, automation:migrate-git
|
||||||
* Validation health, detect, drift, check:syntax, check:version, ...
|
* Validation health, detect, drift, check:syntax, check:version, ...
|
||||||
* Release release, release:joomla, release:create, release:publish, ...
|
* Release release, release:joomla, release:create, release:publish, ...
|
||||||
* Version version:read, version:bump, version:auto-bump, ...
|
* Version version:read, version:bump, version:auto-bump, ...
|
||||||
@@ -91,7 +91,7 @@ const COMMAND_MAP = [
|
|||||||
'sync' => 'automation/bulk_sync.php',
|
'sync' => 'automation/bulk_sync.php',
|
||||||
'deps:update' => 'automation/update_dependencies.php',
|
'deps:update' => 'automation/update_dependencies.php',
|
||||||
'automation:cleanup' => 'automation/repo_cleanup.php',
|
'automation:cleanup' => 'automation/repo_cleanup.php',
|
||||||
'automation:migrate-gitea' => 'automation/migrate_to_gitea.php',
|
'automation:migrate-git' => 'automation/migrate_to_git.php',
|
||||||
|
|
||||||
// Maintenance
|
// Maintenance
|
||||||
'inventory' => 'maintenance/update_repo_inventory.php',
|
'inventory' => 'maintenance/update_repo_inventory.php',
|
||||||
|
|||||||
+2
-2
@@ -14,8 +14,8 @@
|
|||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Enterprise.CLI
|
* DEFGROUP: MokoCLI.Enterprise.CLI
|
||||||
* INGROUP: MokoPlatform.Enterprise
|
* INGROUP: MokoCLI.Enterprise
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/audit_query.php
|
* PATH: /cli/audit_query.php
|
||||||
* BRIEF: Search, filter, and export audit logs
|
* BRIEF: Search, filter, and export audit logs
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ if (isset($options['help']) || empty($options['token'])) {
|
|||||||
echo "Usage: php cli/branch_protect_org.php --token TOKEN [--org ORG] [--api-base URL] [--dry-run]\n";
|
echo "Usage: php cli/branch_protect_org.php --token TOKEN [--org ORG] [--api-base URL] [--dry-run]\n";
|
||||||
echo "\n";
|
echo "\n";
|
||||||
echo "Options:\n";
|
echo "Options:\n";
|
||||||
echo " --token Gitea API token (required)\n";
|
echo " --token Git API token (required)\n";
|
||||||
echo " --org Organization name (default: MokoConsulting)\n";
|
echo " --org Organization name (default: MokoConsulting)\n";
|
||||||
echo " --api-base API base URL (default: https://git.mokoconsulting.tech/api/v1)\n";
|
echo " --api-base API base URL (default: https://git.mokoconsulting.tech/api/v1)\n";
|
||||||
echo " --dry-run Show what would be changed without making changes\n";
|
echo " --dry-run Show what would be changed without making changes\n";
|
||||||
|
|||||||
+39
-19
@@ -10,8 +10,8 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/branch_rename.php
|
* PATH: /cli/branch_rename.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old)
|
* BRIEF: Rename a git branch via Git API (create new, update PR, delete old)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -24,7 +24,7 @@ class BranchRenameCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Rename a git branch via Gitea API (create new, update PR, delete old)');
|
$this->setDescription('Rename a git branch via Git API (create new, update PR, delete old)');
|
||||||
$this->addArgument('--from', 'Source branch name', '');
|
$this->addArgument('--from', 'Source branch name', '');
|
||||||
$this->addArgument('--to', 'Target branch name', '');
|
$this->addArgument('--to', 'Target branch name', '');
|
||||||
$this->addArgument('--token', 'API token', '');
|
$this->addArgument('--token', 'API token', '');
|
||||||
@@ -64,26 +64,46 @@ class BranchRenameCli extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Delete target branch if it already exists
|
// Step 2: Point the target branch at the source commit.
|
||||||
|
// If the target already exists (e.g. a protected `rc` branch that cannot be
|
||||||
|
// deleted), force-update its ref in place instead of delete+recreate: deleting a
|
||||||
|
// protected branch fails, which then makes the recreate return HTTP 409.
|
||||||
|
$sourceSha = '';
|
||||||
|
if (isset($check['body']['commit']['id']) && is_string($check['body']['commit']['id'])) {
|
||||||
|
$sourceSha = $check['body']['commit']['id'];
|
||||||
|
}
|
||||||
$targetCheck = $this->apiRequest('GET', "{$apiBase}/branches/{$to}", $headers);
|
$targetCheck = $this->apiRequest('GET', "{$apiBase}/branches/{$to}", $headers);
|
||||||
if ($targetCheck['code'] === 200) {
|
if ($targetCheck['code'] === 200) {
|
||||||
echo "Target branch '{$to}' already exists — deleting\n";
|
echo "Target branch '{$to}' already exists - force-updating to {$from}
|
||||||
|
";
|
||||||
if (!$this->dryRun) {
|
if (!$this->dryRun) {
|
||||||
$this->apiRequest('DELETE', "{$apiBase}/branches/{$to}", $headers);
|
if ($sourceSha === '') {
|
||||||
|
$this->log('ERROR', "Cannot resolve HEAD commit of source '{$from}'");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
$ref = $this->apiRequest('PATCH', "{$apiBase}/git/refs/heads/{$to}", $headers, [
|
||||||
|
'sha' => $sourceSha,
|
||||||
|
'force' => true,
|
||||||
|
]);
|
||||||
|
if ($ref['code'] < 200 || $ref['code'] >= 300) {
|
||||||
|
$this->log('ERROR', "Failed to force-update '{$to}': HTTP {$ref['code']} (needs force-push perm)");
|
||||||
|
$this->log('ERROR', json_encode($ref['body']));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
echo "Creating branch: {$to} (from {$from})
|
||||||
// Step 3: Create new branch from source
|
";
|
||||||
echo "Creating branch: {$to} (from {$from})\n";
|
if (!$this->dryRun) {
|
||||||
if (!$this->dryRun) {
|
$create = $this->apiRequest('POST', "{$apiBase}/branches", $headers, [
|
||||||
$create = $this->apiRequest('POST', "{$apiBase}/branches", $headers, [
|
'new_branch_name' => $to,
|
||||||
'new_branch_name' => $to,
|
'old_branch_name' => $from,
|
||||||
'old_branch_name' => $from,
|
]);
|
||||||
]);
|
if ($create['code'] < 200 || $create['code'] >= 300) {
|
||||||
if ($create['code'] < 200 || $create['code'] >= 300) {
|
$this->log('ERROR', "Failed to create branch '{$to}': HTTP {$create['code']}");
|
||||||
$this->log('ERROR', "Failed to create branch '{$to}': HTTP {$create['code']}");
|
$this->log('ERROR', json_encode($create['body']));
|
||||||
$this->log('ERROR', json_encode($create['body']));
|
return 1;
|
||||||
return 1;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-19
@@ -12,8 +12,8 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/bulk_workflow_push.php
|
* PATH: /cli/bulk_workflow_push.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Push a workflow file to all governed repos via the Gitea Contents API
|
* BRIEF: Push a workflow file to all governed repos via the Git Contents API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -31,18 +31,18 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Push a workflow file to all governed repos via the Gitea Contents API');
|
$this->setDescription('Push a workflow file to all governed repos via the Git Contents API');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
$this->addArgument('--git-url', 'Git URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--org', 'Target organization', '');
|
$this->addArgument('--org', 'Target organization', '');
|
||||||
$this->addArgument('--file', 'Local workflow file to push', '');
|
$this->addArgument('--file', 'Local workflow file to push', '');
|
||||||
$this->addArgument('--dest', 'Destination path in repos (default: .mokogitea/workflows/<filename>)', '');
|
$this->addArgument('--dest', 'Destination path in repos (default: .mokogit/workflows/<filename>)', '');
|
||||||
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$giteaUrl = rtrim($this->getArgument('--gitea-url'), '/');
|
$gitUrl = rtrim($this->getArgument('--git-url'), '/');
|
||||||
$token = $this->getArgument('--token');
|
$token = $this->getArgument('--token');
|
||||||
$org = $this->getArgument('--org');
|
$org = $this->getArgument('--org');
|
||||||
$workflowFile = $this->getArgument('--file');
|
$workflowFile = $this->getArgument('--file');
|
||||||
@@ -70,7 +70,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($destPath === '') {
|
if ($destPath === '') {
|
||||||
$destPath = '.mokogitea/workflows/' . basename($workflowFile);
|
$destPath = '.mokogit/workflows/' . basename($workflowFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
$localContent = file_get_contents($workflowFile);
|
$localContent = file_get_contents($workflowFile);
|
||||||
@@ -82,7 +82,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
|
|
||||||
$this->log('INFO', "Pushing: {$workflowFile}");
|
$this->log('INFO', "Pushing: {$workflowFile}");
|
||||||
$this->log('INFO', " -> {$destPath} (branch: {$branch})");
|
$this->log('INFO', " -> {$destPath} (branch: {$branch})");
|
||||||
$this->log('INFO', " -> Org: {$org} @ {$giteaUrl}");
|
$this->log('INFO', " -> Org: {$org} @ {$gitUrl}");
|
||||||
|
|
||||||
if ($this->dryRun) {
|
if ($this->dryRun) {
|
||||||
$this->log('INFO', '[DRY RUN] No changes will be made.');
|
$this->log('INFO', '[DRY RUN] No changes will be made.');
|
||||||
@@ -90,7 +90,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
|
|
||||||
echo "\n";
|
echo "\n";
|
||||||
|
|
||||||
$repos = $this->fetchOrgRepos($giteaUrl, $token, $org);
|
$repos = $this->fetchOrgRepos($gitUrl, $token, $org);
|
||||||
|
|
||||||
if ($repos === null) {
|
if ($repos === null) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -104,7 +104,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
$encodedContent = base64_encode($localContent);
|
$encodedContent = base64_encode($localContent);
|
||||||
|
|
||||||
foreach ($repos as $repo) {
|
foreach ($repos as $repo) {
|
||||||
$this->pushToRepo($giteaUrl, $token, $repo, $encodedContent, $localContent, $destPath, $branch);
|
$this->pushToRepo($gitUrl, $token, $repo, $encodedContent, $localContent, $destPath, $branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "\n";
|
echo "\n";
|
||||||
@@ -115,7 +115,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function pushToRepo(
|
private function pushToRepo(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $repoFullName,
|
string $repoFullName,
|
||||||
string $encodedContent,
|
string $encodedContent,
|
||||||
@@ -126,7 +126,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
[$owner, $repoName] = explode('/', $repoFullName, 2);
|
[$owner, $repoName] = explode('/', $repoFullName, 2);
|
||||||
|
|
||||||
$existing = $this->apiRequest(
|
$existing = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/repos/{$owner}/{$repoName}/contents/"
|
"/api/v1/repos/{$owner}/{$repoName}/contents/"
|
||||||
@@ -159,7 +159,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'PUT',
|
'PUT',
|
||||||
"/api/v1/repos/{$owner}/{$repoName}/contents/"
|
"/api/v1/repos/{$owner}/{$repoName}/contents/"
|
||||||
@@ -189,7 +189,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'POST',
|
'POST',
|
||||||
"/api/v1/repos/{$owner}/{$repoName}/contents/"
|
"/api/v1/repos/{$owner}/{$repoName}/contents/"
|
||||||
@@ -210,7 +210,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchOrgRepos(string $giteaUrl, string $token, string $org): ?array
|
private function fetchOrgRepos(string $gitUrl, string $token, string $org): ?array
|
||||||
{
|
{
|
||||||
$this->log('INFO', "Fetching repos from org: {$org}");
|
$this->log('INFO', "Fetching repos from org: {$org}");
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/orgs/{$org}/repos?"
|
"/api/v1/orgs/{$org}/repos?"
|
||||||
@@ -261,13 +261,13 @@ class BulkWorkflowPushCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function apiRequest(
|
private function apiRequest(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $method,
|
string $method,
|
||||||
string $endpoint,
|
string $endpoint,
|
||||||
?string $body = null
|
?string $body = null
|
||||||
): array {
|
): array {
|
||||||
$url = $giteaUrl . $endpoint;
|
$url = $gitUrl . $endpoint;
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/bulk_workflow_trigger.php
|
* PATH: /cli/bulk_workflow_trigger.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Trigger a workflow across multiple repos at once
|
* BRIEF: Trigger a workflow across multiple repos at once
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ use MokoCli\CliFramework;
|
|||||||
|
|
||||||
class BulkWorkflowTriggerCli extends CliFramework
|
class BulkWorkflowTriggerCli extends CliFramework
|
||||||
{
|
{
|
||||||
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
private string $gitUrl = 'https://git.mokoconsulting.tech';
|
||||||
private string $token = '';
|
private string $token = '';
|
||||||
private string $reposFile = '';
|
private string $reposFile = '';
|
||||||
private string $org = '';
|
private string $org = '';
|
||||||
@@ -35,8 +35,8 @@ class BulkWorkflowTriggerCli extends CliFramework
|
|||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Trigger a workflow across multiple repos at once');
|
$this->setDescription('Trigger a workflow across multiple repos at once');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
$this->addArgument('--git-url', 'Git URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--repos', 'File with newline-separated owner/repo list', '');
|
$this->addArgument('--repos', 'File with newline-separated owner/repo list', '');
|
||||||
$this->addArgument('--org', 'Trigger on all repos in an org', '');
|
$this->addArgument('--org', 'Trigger on all repos in an org', '');
|
||||||
$this->addArgument('--workflow', 'Workflow file (e.g., "sync-servers.yml")', '');
|
$this->addArgument('--workflow', 'Workflow file (e.g., "sync-servers.yml")', '');
|
||||||
@@ -46,7 +46,7 @@ class BulkWorkflowTriggerCli extends CliFramework
|
|||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$this->giteaUrl = rtrim($this->getArgument('--gitea-url'), '/');
|
$this->gitUrl = rtrim($this->getArgument('--git-url'), '/');
|
||||||
$this->token = $this->getArgument('--token');
|
$this->token = $this->getArgument('--token');
|
||||||
$this->reposFile = $this->getArgument('--repos');
|
$this->reposFile = $this->getArgument('--repos');
|
||||||
$this->org = $this->getArgument('--org');
|
$this->org = $this->getArgument('--org');
|
||||||
@@ -78,7 +78,7 @@ class BulkWorkflowTriggerCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->log('INFO', "Triggering workflow \"{$this->workflow}\" on ref \"{$this->ref}\" across " . count($repos) . " repo(s).");
|
$this->log('INFO', "Triggering workflow \"{$this->workflow}\" on ref \"{$this->ref}\" across " . count($repos) . " repo(s).");
|
||||||
$this->log('INFO', "Gitea URL: {$this->giteaUrl}");
|
$this->log('INFO', "Git URL: {$this->gitUrl}");
|
||||||
|
|
||||||
if ($this->dryRun) {
|
if ($this->dryRun) {
|
||||||
$this->log('INFO', '[DRY RUN] No requests will be sent.');
|
$this->log('INFO', '[DRY RUN] No requests will be sent.');
|
||||||
@@ -209,7 +209,7 @@ class BulkWorkflowTriggerCli extends CliFramework
|
|||||||
|
|
||||||
private function apiRequest(string $method, string $endpoint, ?string $body = null): array
|
private function apiRequest(string $method, string $endpoint, ?string $body = null): array
|
||||||
{
|
{
|
||||||
$url = $this->giteaUrl . $endpoint;
|
$url = $this->gitUrl . $endpoint;
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================================
|
||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Automation.CI
|
||||||
|
# INGROUP: mokocli.CLI
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
|
# PATH: /cli/ci_issue_reporter.sh
|
||||||
|
# VERSION: 10.00.00
|
||||||
|
# BRIEF: Creates or updates a Git issue when a CI gate fails.
|
||||||
|
# Deduplicates by searching open issues with the "ci-auto" label
|
||||||
|
# whose title matches the gate. If a matching issue exists, a comment
|
||||||
|
# is appended instead of opening a duplicate.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Defaults ────────────────────────────────────────────────────────────────
|
||||||
|
MOKOGIT_URL="${MOKOGIT_URL:-https://git.mokoconsulting.tech}"
|
||||||
|
MOKOGIT_TOKEN="${MOKOGIT_TOKEN:-}"
|
||||||
|
REPO="${GITHUB_REPOSITORY:-}"
|
||||||
|
RUN_URL="${GITHUB_SERVER_URL:-${MOKOGIT_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
|
||||||
|
LABEL_NAME="ci-auto"
|
||||||
|
LABEL_COLOR="#e11d48"
|
||||||
|
|
||||||
|
GATE=""
|
||||||
|
DETAILS=""
|
||||||
|
SEVERITY="error"
|
||||||
|
WORKFLOW=""
|
||||||
|
|
||||||
|
# ── Parse arguments ─────────────────────────────────────────────────────────
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: ci_issue_reporter.sh --gate NAME --details TEXT [OPTIONS]
|
||||||
|
|
||||||
|
Required:
|
||||||
|
--gate CI gate name (e.g. "Code Quality", "Self-Health")
|
||||||
|
--details Human-readable failure description
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
--severity "error" (default) or "warning"
|
||||||
|
--workflow Workflow name for the issue title
|
||||||
|
--repo owner/repo (default: \$GITHUB_REPOSITORY)
|
||||||
|
--run-url URL to the CI run (auto-detected from env)
|
||||||
|
--token Git API token (default: \$MOKOGIT_TOKEN)
|
||||||
|
--url Git base URL (default: \$MOKOGIT_URL)
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--gate) GATE="$2"; shift 2 ;;
|
||||||
|
--details) DETAILS="$2"; shift 2 ;;
|
||||||
|
--severity) SEVERITY="$2"; shift 2 ;;
|
||||||
|
--workflow) WORKFLOW="$2"; shift 2 ;;
|
||||||
|
--repo) REPO="$2"; shift 2 ;;
|
||||||
|
--run-url) RUN_URL="$2"; shift 2 ;;
|
||||||
|
--token) MOKOGIT_TOKEN="$2"; shift 2 ;;
|
||||||
|
--url) MOKOGIT_URL="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage ;;
|
||||||
|
*) echo "Unknown option: $1"; usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
|
||||||
|
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
|
||||||
|
[[ -z "$MOKOGIT_TOKEN" ]] && { echo "ERROR: MOKOGIT_TOKEN not set"; exit 1; }
|
||||||
|
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
|
||||||
|
|
||||||
|
API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
|
||||||
|
|
||||||
|
# ── Build title ─────────────────────────────────────────────────────────────
|
||||||
|
if [[ -n "$WORKFLOW" ]]; then
|
||||||
|
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
|
||||||
|
else
|
||||||
|
TITLE="[CI] ${GATE} failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Ensure label exists ─────────────────────────────────────────────────────
|
||||||
|
ensure_label() {
|
||||||
|
local exists
|
||||||
|
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
"${API}/labels" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$exists" == "200" ]]; then
|
||||||
|
local found
|
||||||
|
found=$(curl -sf \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
"${API}/labels" 2>/dev/null \
|
||||||
|
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
|
||||||
|
|
||||||
|
if [[ -z "$found" ]]; then
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/labels" \
|
||||||
|
-d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \
|
||||||
|
> /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Search for existing open issue ──────────────────────────────────────────
|
||||||
|
find_existing_issue() {
|
||||||
|
local query
|
||||||
|
query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g')
|
||||||
|
|
||||||
|
local response
|
||||||
|
response=$(curl -sf \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
|
||||||
|
2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
echo "$response" \
|
||||||
|
| grep -oP '"number":\s*\K[0-9]+' \
|
||||||
|
| head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Build issue body ────────────────────────────────────────────────────────
|
||||||
|
build_body() {
|
||||||
|
local severity_badge
|
||||||
|
if [[ "$SEVERITY" == "error" ]]; then
|
||||||
|
severity_badge="**Severity:** Error"
|
||||||
|
else
|
||||||
|
severity_badge="**Severity:** Warning"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<BODY
|
||||||
|
## CI Gate Failure: ${GATE}
|
||||||
|
|
||||||
|
${severity_badge}
|
||||||
|
**Workflow:** ${WORKFLOW:-unknown}
|
||||||
|
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||||
|
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||||
|
**Run:** [View CI run](${RUN_URL})
|
||||||
|
|
||||||
|
### Details
|
||||||
|
|
||||||
|
${DETAILS}
|
||||||
|
|
||||||
|
### Resolution
|
||||||
|
|
||||||
|
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Auto-created by [ci-issue-reporter](${MOKOGIT_URL}/MokoConsulting/MokoCLI/src/branch/main/cli/ci_issue_reporter.sh)*
|
||||||
|
BODY
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Build comment body (for existing issues) ────────────────────────────────
|
||||||
|
build_comment() {
|
||||||
|
cat <<COMMENT
|
||||||
|
### CI failure recurrence
|
||||||
|
|
||||||
|
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||||
|
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||||
|
**Run:** [View CI run](${RUN_URL})
|
||||||
|
|
||||||
|
${DETAILS}
|
||||||
|
COMMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
ensure_label
|
||||||
|
|
||||||
|
EXISTING=$(find_existing_issue)
|
||||||
|
|
||||||
|
if [[ -n "$EXISTING" ]]; then
|
||||||
|
COMMENT_BODY=$(build_comment)
|
||||||
|
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
|
||||||
|
|
||||||
|
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/issues/${EXISTING}/comments" \
|
||||||
|
-d "${COMMENT_JSON}" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$HTTP" == "201" ]]; then
|
||||||
|
echo "Commented on existing issue #${EXISTING}"
|
||||||
|
else
|
||||||
|
echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ISSUE_BODY=$(build_body)
|
||||||
|
ISSUE_JSON=$(python3 -c "
|
||||||
|
import sys, json
|
||||||
|
body = sys.stdin.read()
|
||||||
|
print(json.dumps({
|
||||||
|
'title': sys.argv[1],
|
||||||
|
'body': body,
|
||||||
|
'labels': []
|
||||||
|
}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null)
|
||||||
|
|
||||||
|
RESPONSE=$(curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/issues" \
|
||||||
|
-d "${ISSUE_JSON}" 2>/dev/null || echo "{}")
|
||||||
|
|
||||||
|
ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1)
|
||||||
|
|
||||||
|
if [[ -n "$ISSUE_NUM" ]]; then
|
||||||
|
LABEL_ID=$(curl -sf \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
"${API}/labels" 2>/dev/null \
|
||||||
|
| grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \
|
||||||
|
| head -1 || true)
|
||||||
|
|
||||||
|
if [[ -n "$LABEL_ID" ]]; then
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/issues/${ISSUE_NUM}/labels" \
|
||||||
|
-d "{\"labels\":[${LABEL_ID}]}" \
|
||||||
|
> /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Created issue #${ISSUE_NUM}: ${TITLE}"
|
||||||
|
else
|
||||||
|
echo "WARNING: Failed to create issue"
|
||||||
|
echo "Response: ${RESPONSE}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
+10
-10
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/client_dashboard.php
|
* PATH: /cli/client_dashboard.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Generate unified client dashboard HTML
|
* BRIEF: Generate unified client dashboard HTML
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ use MokoCli\CliFramework;
|
|||||||
|
|
||||||
class ClientDashboardCli extends CliFramework
|
class ClientDashboardCli extends CliFramework
|
||||||
{
|
{
|
||||||
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
private string $gitUrl = 'https://git.mokoconsulting.tech';
|
||||||
private string $token = '';
|
private string $token = '';
|
||||||
private string $org = 'MokoConsulting';
|
private string $org = 'MokoConsulting';
|
||||||
private string $outputFile = '';
|
private string $outputFile = '';
|
||||||
@@ -36,8 +36,8 @@ class ClientDashboardCli extends CliFramework
|
|||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Generate unified client dashboard HTML');
|
$this->setDescription('Generate unified client dashboard HTML');
|
||||||
$this->addArgument('--token', 'Gitea token (or MOKOGITEA_TOKEN)', '');
|
$this->addArgument('--token', 'Git token (or MOKOGIT_TOKEN)', '');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL', 'https://git.mokoconsulting.tech');
|
$this->addArgument('--git-url', 'Git URL', 'https://git.mokoconsulting.tech');
|
||||||
$this->addArgument('--org', 'Primary org (default: MokoConsulting)', 'MokoConsulting');
|
$this->addArgument('--org', 'Primary org (default: MokoConsulting)', 'MokoConsulting');
|
||||||
$this->addArgument('--output', 'Output HTML file (default: stdout)', '');
|
$this->addArgument('--output', 'Output HTML file (default: stdout)', '');
|
||||||
$this->addArgument('-o', 'Output HTML file (alias)', '');
|
$this->addArgument('-o', 'Output HTML file (alias)', '');
|
||||||
@@ -48,7 +48,7 @@ class ClientDashboardCli extends CliFramework
|
|||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$this->giteaUrl = rtrim($this->getArgument('--gitea-url'), '/');
|
$this->gitUrl = rtrim($this->getArgument('--git-url'), '/');
|
||||||
$this->token = $this->getArgument('--token');
|
$this->token = $this->getArgument('--token');
|
||||||
$this->org = $this->getArgument('--org');
|
$this->org = $this->getArgument('--org');
|
||||||
$this->outputFile = $this->getArgument('--output') ?: $this->getArgument('-o');
|
$this->outputFile = $this->getArgument('--output') ?: $this->getArgument('-o');
|
||||||
@@ -57,11 +57,11 @@ class ClientDashboardCli extends CliFramework
|
|||||||
$this->sslWarnDays = (int) $this->getArgument('--ssl-warn-days');
|
$this->sslWarnDays = (int) $this->getArgument('--ssl-warn-days');
|
||||||
|
|
||||||
if ($this->token === '') {
|
if ($this->token === '') {
|
||||||
$this->token = getenv('MOKOGITEA_TOKEN') ?: '';
|
$this->token = getenv('MOKOGIT_TOKEN') ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->token === '') {
|
if ($this->token === '') {
|
||||||
$this->log('ERROR', '--token or MOKOGITEA_TOKEN required.');
|
$this->log('ERROR', '--token or MOKOGIT_TOKEN required.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ class ClientDashboardCli extends CliFramework
|
|||||||
$name = $repo['name'] ?? '';
|
$name = $repo['name'] ?? '';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!str_starts_with($name, 'client-waas-')
|
!str_starts_with($name, 'client-waas-') && !str_starts_with($name, 'client-mokosuite-')
|
||||||
|| !empty($repo['archived'])
|
|| !empty($repo['archived'])
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
@@ -135,7 +135,7 @@ class ClientDashboardCli extends CliFramework
|
|||||||
|
|
||||||
$clients[] = [
|
$clients[] = [
|
||||||
'repo' => $repo['full_name'] ?? '',
|
'repo' => $repo['full_name'] ?? '',
|
||||||
'name' => str_replace('client-waas-', '', $name),
|
'name' => str_replace(['client-waas-', 'client-mokosuite-'], '', $name),
|
||||||
'description' => $repo['description'] ?? '',
|
'description' => $repo['description'] ?? '',
|
||||||
'updated' => $repo['updated_at'] ?? '',
|
'updated' => $repo['updated_at'] ?? '',
|
||||||
'url' => $repo['html_url'] ?? '',
|
'url' => $repo['html_url'] ?? '',
|
||||||
@@ -460,7 +460,7 @@ CARD;
|
|||||||
/** @return array{code: int, body: string} */
|
/** @return array{code: int, body: string} */
|
||||||
private function api(string $method, string $endpoint): array
|
private function api(string $method, string $endpoint): array
|
||||||
{
|
{
|
||||||
$url = $this->giteaUrl . $endpoint;
|
$url = $this->gitUrl . $endpoint;
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/client_inventory.php
|
* PATH: /cli/client_inventory.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -24,21 +24,21 @@ use MokoCli\CliFramework;
|
|||||||
|
|
||||||
class ClientInventoryCli extends CliFramework
|
class ClientInventoryCli extends CliFramework
|
||||||
{
|
{
|
||||||
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
private string $gitUrl = 'https://git.mokoconsulting.tech';
|
||||||
private string $token = '';
|
private string $token = '';
|
||||||
private bool $jsonOutput = false;
|
private bool $jsonOutput = false;
|
||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Discover and list all client-waas repos with their server configuration status');
|
$this->setDescription('Discover and list all client-waas repos with their server configuration status');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
$this->addArgument('--git-url', 'Git URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--json', 'Output results as JSON', false);
|
$this->addArgument('--json', 'Output results as JSON', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$this->giteaUrl = rtrim($this->getArgument('--gitea-url'), '/');
|
$this->gitUrl = rtrim($this->getArgument('--git-url'), '/');
|
||||||
$this->token = $this->getArgument('--token');
|
$this->token = $this->getArgument('--token');
|
||||||
$this->jsonOutput = (bool) $this->getArgument('--json');
|
$this->jsonOutput = (bool) $this->getArgument('--json');
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class ClientInventoryCli extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->log('INFO', "Scanning Gitea instance: {$this->giteaUrl}");
|
$this->log('INFO', "Scanning Git instance: {$this->gitUrl}");
|
||||||
|
|
||||||
// Step 1: List all orgs
|
// Step 1: List all orgs
|
||||||
$orgs = $this->fetchOrgs();
|
$orgs = $this->fetchOrgs();
|
||||||
@@ -79,7 +79,7 @@ class ClientInventoryCli extends CliFramework
|
|||||||
foreach ($repos as $repo) {
|
foreach ($repos as $repo) {
|
||||||
$repoName = $repo['name'] ?? '';
|
$repoName = $repo['name'] ?? '';
|
||||||
|
|
||||||
if (strpos($repoName, 'client-waas') === false) {
|
if (strpos($repoName, 'client-waas') === false && strpos($repoName, 'client-mokosuite') === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ class ClientInventoryCli extends CliFramework
|
|||||||
|
|
||||||
private function apiRequest(string $method, string $endpoint, ?string $body = null): array
|
private function apiRequest(string $method, string $endpoint, ?string $body = null): array
|
||||||
{
|
{
|
||||||
$url = $this->giteaUrl . $endpoint;
|
$url = $this->gitUrl . $endpoint;
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|||||||
+23
-23
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/client_provision.php
|
* PATH: /cli/client_provision.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Provision a new client environment end-to-end
|
* BRIEF: Provision a new client environment end-to-end
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ use MokoCli\CliFramework;
|
|||||||
|
|
||||||
class ClientProvisionCli extends CliFramework
|
class ClientProvisionCli extends CliFramework
|
||||||
{
|
{
|
||||||
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
private string $gitUrl = 'https://git.mokoconsulting.tech';
|
||||||
private string $giteaToken = '';
|
private string $gitToken = '';
|
||||||
private string $grafanaUrl = '';
|
private string $grafanaUrl = '';
|
||||||
private string $grafanaToken = '';
|
private string $grafanaToken = '';
|
||||||
private string $configFile = '';
|
private string $configFile = '';
|
||||||
@@ -65,17 +65,17 @@ class ClientProvisionCli extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->giteaToken = $this->config['gitea_token']
|
$this->gitToken = $this->config['git_token']
|
||||||
?? getenv('MOKOGITEA_TOKEN') ?: '';
|
?? getenv('MOKOGIT_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']
|
||||||
?? getenv('GRAFANA_TOKEN') ?: '';
|
?? getenv('GRAFANA_TOKEN') ?: '';
|
||||||
$this->giteaUrl = $this->config['gitea_url']
|
$this->gitUrl = $this->config['git_url']
|
||||||
?? $this->giteaUrl;
|
?? $this->gitUrl;
|
||||||
|
|
||||||
if ($this->giteaToken === '') {
|
if ($this->gitToken === '') {
|
||||||
$this->log('ERROR', 'gitea_token or MOKOGITEA_TOKEN required.');
|
$this->log('ERROR', 'git_token or MOKOGIT_TOKEN required.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ class ClientProvisionCli extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->repoName = 'client-waas-' . $clientName;
|
$this->repoName = 'client-mokosuite-' . $clientName;
|
||||||
|
|
||||||
$this->log('INFO', "=== Client Provisioning: {$clientName} ===");
|
$this->log('INFO', "=== Client Provisioning: {$clientName} ===");
|
||||||
$this->log('INFO', " Org: {$this->org}");
|
$this->log('INFO', " Org: {$this->org}");
|
||||||
@@ -104,7 +104,7 @@ class ClientProvisionCli extends CliFramework
|
|||||||
'variables' => 'setVariables',
|
'variables' => 'setVariables',
|
||||||
'secrets' => 'setSecrets',
|
'secrets' => 'setSecrets',
|
||||||
'monitoring' => 'setupMonitoring',
|
'monitoring' => 'setupMonitoring',
|
||||||
'summary' => 'printSummary',
|
'summary' => 'printProvisioningSummary',
|
||||||
];
|
];
|
||||||
|
|
||||||
$exitCode = 0;
|
$exitCode = 0;
|
||||||
@@ -128,7 +128,7 @@ class ClientProvisionCli extends CliFramework
|
|||||||
{
|
{
|
||||||
$this->log('INFO', '[1/5] Creating repository...');
|
$this->log('INFO', '[1/5] Creating repository...');
|
||||||
|
|
||||||
$check = $this->giteaApi(
|
$check = $this->gitApi(
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/repos/{$this->org}/{$this->repoName}"
|
"/api/v1/repos/{$this->org}/{$this->repoName}"
|
||||||
);
|
);
|
||||||
@@ -153,10 +153,10 @@ class ClientProvisionCli extends CliFramework
|
|||||||
'labels' => true,
|
'labels' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$resp = $this->giteaApi(
|
$resp = $this->gitApi(
|
||||||
'POST',
|
'POST',
|
||||||
'/api/v1/repos/MokoConsulting/'
|
'/api/v1/repos/MokoConsulting/'
|
||||||
. 'Template-Client-WaaS/generate',
|
. 'Template-Client/generate',
|
||||||
$payload
|
$payload
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ class ClientProvisionCli extends CliFramework
|
|||||||
|
|
||||||
$this->log('INFO', ' OK: Repo created');
|
$this->log('INFO', ' OK: Repo created');
|
||||||
|
|
||||||
$this->giteaApi(
|
$this->gitApi(
|
||||||
'POST',
|
'POST',
|
||||||
"/api/v1/repos/{$this->org}/{$this->repoName}/branches",
|
"/api/v1/repos/{$this->org}/{$this->repoName}/branches",
|
||||||
json_encode([
|
json_encode([
|
||||||
@@ -250,7 +250,7 @@ class ClientProvisionCli extends CliFramework
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$resp = $this->giteaApi(
|
$resp = $this->gitApi(
|
||||||
'PUT',
|
'PUT',
|
||||||
"{$api}/{$name}",
|
"{$api}/{$name}",
|
||||||
json_encode(['data' => $value])
|
json_encode(['data' => $value])
|
||||||
@@ -383,7 +383,7 @@ class ClientProvisionCli extends CliFramework
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function printSummary(): int
|
private function printProvisioningSummary(): int
|
||||||
{
|
{
|
||||||
$vars = $this->config['variables'] ?? [];
|
$vars = $this->config['variables'] ?? [];
|
||||||
$secrets = $this->config['secrets'] ?? [];
|
$secrets = $this->config['secrets'] ?? [];
|
||||||
@@ -391,7 +391,7 @@ class ClientProvisionCli extends CliFramework
|
|||||||
echo "\n";
|
echo "\n";
|
||||||
$this->log('INFO', '[5/5] Provisioning summary');
|
$this->log('INFO', '[5/5] Provisioning summary');
|
||||||
echo str_repeat('=', 60) . "\n";
|
echo str_repeat('=', 60) . "\n";
|
||||||
echo " Repo: {$this->giteaUrl}/{$this->org}/{$this->repoName}\n";
|
echo " Repo: {$this->gitUrl}/{$this->org}/{$this->repoName}\n";
|
||||||
echo ' Variables: ' . count($vars) . " set\n";
|
echo ' Variables: ' . count($vars) . " set\n";
|
||||||
echo ' Secrets: ' . count($secrets) . " set\n";
|
echo ' Secrets: ' . count($secrets) . " set\n";
|
||||||
echo "\n";
|
echo "\n";
|
||||||
@@ -409,14 +409,14 @@ class ClientProvisionCli extends CliFramework
|
|||||||
string $name,
|
string $name,
|
||||||
string $value
|
string $value
|
||||||
): bool {
|
): bool {
|
||||||
$resp = $this->giteaApi(
|
$resp = $this->gitApi(
|
||||||
'PUT',
|
'PUT',
|
||||||
"{$api}/{$name}",
|
"{$api}/{$name}",
|
||||||
json_encode(['value' => $value])
|
json_encode(['value' => $value])
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($resp['code'] === 404) {
|
if ($resp['code'] === 404) {
|
||||||
$resp = $this->giteaApi(
|
$resp = $this->gitApi(
|
||||||
'POST',
|
'POST',
|
||||||
$api,
|
$api,
|
||||||
json_encode(['name' => $name, 'value' => $value])
|
json_encode(['name' => $name, 'value' => $value])
|
||||||
@@ -426,15 +426,15 @@ class ClientProvisionCli extends CliFramework
|
|||||||
return $resp['code'] >= 200 && $resp['code'] < 300;
|
return $resp['code'] >= 200 && $resp['code'] < 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaApi(
|
private function gitApi(
|
||||||
string $method,
|
string $method,
|
||||||
string $endpoint,
|
string $endpoint,
|
||||||
?string $body = null
|
?string $body = null
|
||||||
): array {
|
): array {
|
||||||
return $this->httpRequest(
|
return $this->httpRequest(
|
||||||
$this->giteaUrl . $endpoint,
|
$this->gitUrl . $endpoint,
|
||||||
$method,
|
$method,
|
||||||
"token {$this->giteaToken}",
|
"token {$this->gitToken}",
|
||||||
$body
|
$body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class CreateProjectCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
$config = \MokoCli\Config::load();
|
$config = \MokoCli\Config::load();
|
||||||
$platformName = $config->getString('platform', 'gitea');
|
$platformName = $config->getString('platform', 'git');
|
||||||
try {
|
try {
|
||||||
$adapter = \MokoCli\PlatformAdapterFactory::create($config);
|
$adapter = \MokoCli\PlatformAdapterFactory::create($config);
|
||||||
$api = $adapter->getApiClient();
|
$api = $adapter->getApiClient();
|
||||||
@@ -89,8 +89,8 @@ class CreateProjectCli extends CliFramework
|
|||||||
$this->log('ERROR', "Platform initialization failed: " . $e->getMessage());
|
$this->log('ERROR', "Platform initialization failed: " . $e->getMessage());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
$token = $platformName === 'gitea'
|
$token = $platformName === 'git'
|
||||||
? $config->getString('gitea.token', '')
|
? $config->getString('git.token', '')
|
||||||
: $config->getString('github.token', '');
|
: $config->getString('github.token', '');
|
||||||
|
|
||||||
$repoRoot = dirname(__DIR__, 2);
|
$repoRoot = dirname(__DIR__, 2);
|
||||||
@@ -173,7 +173,7 @@ class CreateProjectCli extends CliFramework
|
|||||||
return $failed > 0 ? 1 : 0;
|
return $failed > 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function graphql(string $query, array $variables, string $token, string $platformName = 'gitea'): array
|
private function graphql(string $query, array $variables, string $token, string $platformName = 'git'): array
|
||||||
{
|
{
|
||||||
if ($platformName !== 'github') {
|
if ($platformName !== 'github') {
|
||||||
return [];
|
return [];
|
||||||
@@ -223,7 +223,7 @@ class CreateProjectCli extends CliFramework
|
|||||||
|
|
||||||
private function detectRepoPlatform(string $org, string $repo, string $token, ?\MokoCli\ApiClient $apiClient = null): string
|
private function detectRepoPlatform(string $org, string $repo, string $token, ?\MokoCli\ApiClient $apiClient = null): string
|
||||||
{
|
{
|
||||||
foreach (['.github/.mokostandards', '.mokogitea/.mokostandards', '.mokostandards'] as $path) {
|
foreach (['.github/.mokostandards', '.mokogit/.mokostandards', '.mokostandards'] as $path) {
|
||||||
$data = $this->restGet("repos/{$org}/{$repo}/contents/{$path}", $token, $apiClient);
|
$data = $this->restGet("repos/{$org}/{$repo}/contents/{$path}", $token, $apiClient);
|
||||||
if (!empty($data['content'])) {
|
if (!empty($data['content'])) {
|
||||||
$content = base64_decode($data['content']);
|
$content = base64_decode($data['content']);
|
||||||
|
|||||||
+4
-4
@@ -116,14 +116,14 @@ class CreateRepoCli extends CliFramework
|
|||||||
echo " (dry-run) would set topics: " . implode(', ', $topics) . "\n";
|
echo " (dry-run) would set topics: " . implode(', ', $topics) . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "Step 3: Creating .mokogitea/manifest.xml...\n";
|
echo "Step 3: Creating .mokogit/manifest.xml...\n";
|
||||||
$mokoContent = "platform: {$platform}\nversion: 04.02.30\nmanaged: true\n";
|
$mokoContent = "platform: {$platform}\nversion: 04.02.30\nmanaged: true\n";
|
||||||
if (!$this->dryRun) {
|
if (!$this->dryRun) {
|
||||||
try {
|
try {
|
||||||
$adapter->createOrUpdateFile(
|
$adapter->createOrUpdateFile(
|
||||||
$org,
|
$org,
|
||||||
$name,
|
$name,
|
||||||
'.mokogitea/manifest.xml',
|
'.mokogit/manifest.xml',
|
||||||
$mokoContent,
|
$mokoContent,
|
||||||
'chore: add manifest.xml platform config [skip ci]'
|
'chore: add manifest.xml platform config [skip ci]'
|
||||||
);
|
);
|
||||||
@@ -132,11 +132,11 @@ class CreateRepoCli extends CliFramework
|
|||||||
echo " Warning: " . $e->getMessage() . "\n";
|
echo " Warning: " . $e->getMessage() . "\n";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
echo " (dry-run) would create .mokogitea/manifest.xml\n";
|
echo " (dry-run) would create .mokogit/manifest.xml\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "Step 4: Creating README.md...\n";
|
echo "Step 4: Creating README.md...\n";
|
||||||
$baseUrl = $platformName === 'gitea' ? $config->getString('gitea.url', 'https://git.mokoconsulting.tech') : 'https://github.com';
|
$baseUrl = $platformName === 'git' ? $config->getString('git.url', 'https://git.mokoconsulting.tech') : 'https://github.com';
|
||||||
$repoUrl = "{$baseUrl}/{$org}/{$name}";
|
$repoUrl = "{$baseUrl}/{$org}/{$name}";
|
||||||
$standardsUrl = "{$baseUrl}/{$org}/MokoCli";
|
$standardsUrl = "{$baseUrl}/{$org}/MokoCli";
|
||||||
$readmeContent = "<!--\n"
|
$readmeContent = "<!--\n"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.CLI
|
* DEFGROUP: MokoCLI.CLI
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/deploy_joomla.php
|
* PATH: /cli/deploy_joomla.php
|
||||||
* BRIEF: Smart Joomla deploy — routes files to correct server directories by extension type
|
* BRIEF: Smart Joomla deploy — routes files to correct server directories by extension type
|
||||||
@@ -833,7 +833,7 @@ class DeployJoomla extends CliFramework
|
|||||||
/**
|
/**
|
||||||
* Resolve source directory with smart fallback chain:
|
* Resolve source directory with smart fallback chain:
|
||||||
* 1. --src-dir flag (explicit)
|
* 1. --src-dir flag (explicit)
|
||||||
* 2. .mokogitea/manifest.xml <deploy><source-dir>
|
* 2. .mokogit/manifest.xml <deploy><source-dir>
|
||||||
* 3. src/ directory
|
* 3. src/ directory
|
||||||
* 4. htdocs/ directory
|
* 4. htdocs/ directory
|
||||||
* 5. Repo root (for flat-layout extensions)
|
* 5. Repo root (for flat-layout extensions)
|
||||||
@@ -851,8 +851,8 @@ class DeployJoomla extends CliFramework
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Read from .mokogitea/manifest.xml
|
// 2. Read from .mokogit/manifest.xml
|
||||||
$mokoManifest = "{$repoPath}/.mokogitea/manifest.xml";
|
$mokoManifest = "{$repoPath}/.mokogit/manifest.xml";
|
||||||
if (file_exists($mokoManifest)) {
|
if (file_exists($mokoManifest)) {
|
||||||
$xml = @simplexml_load_file($mokoManifest);
|
$xml = @simplexml_load_file($mokoManifest);
|
||||||
if ($xml !== false) {
|
if ($xml !== false) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/dev_branch_reset.php
|
* PATH: /cli/dev_branch_reset.php
|
||||||
* BRIEF: Delete and recreate dev branch from main via Gitea API
|
* BRIEF: Delete and recreate dev branch from main via Git API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,9 +23,9 @@ class DevBranchResetCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Delete and recreate dev branch from main via Gitea API');
|
$this->setDescription('Delete and recreate dev branch from main via Git API');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', '');
|
$this->addArgument('--api-base', 'Git API base URL', '');
|
||||||
$this->addArgument('--branch', 'Branch to reset', 'dev');
|
$this->addArgument('--branch', 'Branch to reset', 'dev');
|
||||||
$this->addArgument('--from', 'Source branch', 'main');
|
$this->addArgument('--from', 'Source branch', 'main');
|
||||||
$this->addArgument('--output-summary', 'Write to $GITHUB_STEP_SUMMARY', false);
|
$this->addArgument('--output-summary', 'Write to $GITHUB_STEP_SUMMARY', false);
|
||||||
@@ -33,7 +33,7 @@ class DevBranchResetCli extends CliFramework
|
|||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$token = $this->getArgument('--token') ?: getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: '';
|
$token = $this->getArgument('--token') ?: getenv('MOKOGIT_TOKEN') ?: getenv('GIT_TOKEN') ?: '';
|
||||||
$apiBase = $this->getArgument('--api-base');
|
$apiBase = $this->getArgument('--api-base');
|
||||||
$branch = $this->getArgument('--branch');
|
$branch = $this->getArgument('--branch');
|
||||||
$from = $this->getArgument('--from');
|
$from = $this->getArgument('--from');
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/grafana_dashboard.php
|
* PATH: /cli/grafana_dashboard.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Manage Grafana dashboards via API
|
* BRIEF: Manage Grafana dashboards via API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/joomla_build.php
|
* PATH: /cli/joomla_build.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
||||||
* NOTE: Called by pre-release and auto-release workflows.
|
* NOTE: Called by pre-release and auto-release workflows.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/joomla_metadata_validate.php
|
* PATH: /cli/joomla_metadata_validate.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Validate MokoGitea repo metadata against Joomla extension manifest XML
|
* BRIEF: Validate MokoGIT repo metadata against Joomla extension manifest XML
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -22,7 +22,7 @@ use MokoCli\CliFramework;
|
|||||||
|
|
||||||
class JoomlaMetadataValidateCli extends CliFramework
|
class JoomlaMetadataValidateCli extends CliFramework
|
||||||
{
|
{
|
||||||
/** Joomla element prefix map — must match MokoGitea's cleanJoomlaElement() */
|
/** Joomla element prefix map — must match MokoGIT's cleanJoomlaElement() */
|
||||||
private const JOOMLA_PREFIX = [
|
private const JOOMLA_PREFIX = [
|
||||||
'package' => 'pkg_',
|
'package' => 'pkg_',
|
||||||
'component' => 'com_',
|
'component' => 'com_',
|
||||||
@@ -34,12 +34,12 @@ class JoomlaMetadataValidateCli extends CliFramework
|
|||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Validate MokoGitea repo metadata against Joomla extension manifest XML');
|
$this->setDescription('Validate MokoGIT repo metadata against Joomla extension manifest XML');
|
||||||
$this->addArgument('--path', 'Repo root path (default: current directory)', '.');
|
$this->addArgument('--path', 'Repo root path (default: current directory)', '.');
|
||||||
$this->addArgument('--token', 'Gitea API token (or GITEA_TOKEN env)', '');
|
$this->addArgument('--token', 'Git API token (or GIT_TOKEN env)', '');
|
||||||
$this->addArgument('--org', 'Gitea org', 'MokoConsulting');
|
$this->addArgument('--org', 'Git org', 'MokoConsulting');
|
||||||
$this->addArgument('--repo', 'Repo name (auto-detected from git if empty)', '');
|
$this->addArgument('--repo', 'Repo name (auto-detected from git if empty)', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
$this->addArgument('--api-base', 'Git API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
||||||
$this->addArgument('--ci', 'CI mode: exit 1 on any error', false);
|
$this->addArgument('--ci', 'CI mode: exit 1 on any error', false);
|
||||||
$this->addArgument('--json', 'Output as JSON', false);
|
$this->addArgument('--json', 'Output as JSON', false);
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ class JoomlaMetadataValidateCli extends CliFramework
|
|||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$path = realpath($this->getArgument('--path')) ?: $this->getArgument('--path');
|
$path = realpath($this->getArgument('--path')) ?: $this->getArgument('--path');
|
||||||
$token = $this->getArgument('--token') ?: getenv('GITEA_TOKEN') ?: '';
|
$token = $this->getArgument('--token') ?: getenv('GIT_TOKEN') ?: '';
|
||||||
$org = $this->getArgument('--org');
|
$org = $this->getArgument('--org');
|
||||||
$repoName = $this->getArgument('--repo');
|
$repoName = $this->getArgument('--repo');
|
||||||
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
||||||
@@ -73,11 +73,11 @@ class JoomlaMetadataValidateCli extends CliFramework
|
|||||||
|
|
||||||
$this->log('INFO', "Joomla manifest: {$joomlaXml['path']}");
|
$this->log('INFO', "Joomla manifest: {$joomlaXml['path']}");
|
||||||
|
|
||||||
// ── Step 2: Load MokoGitea metadata ─────────────────────────
|
// ── Step 2: Load MokoGIT metadata ─────────────────────────
|
||||||
$metadata = $this->loadMetadata($path, $org, $repoName, $token, $apiBase);
|
$metadata = $this->loadMetadata($path, $org, $repoName, $token, $apiBase);
|
||||||
|
|
||||||
if ($metadata === null) {
|
if ($metadata === null) {
|
||||||
$this->log('ERROR', 'Could not load MokoGitea metadata');
|
$this->log('ERROR', 'Could not load MokoGIT metadata');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ class JoomlaMetadataValidateCli extends CliFramework
|
|||||||
private function loadMetadata(string $root, string $org, string $repoName, string $token, string $apiBase): ?array
|
private function loadMetadata(string $root, string $org, string $repoName, string $token, string $apiBase): ?array
|
||||||
{
|
{
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$this->log('ERROR', 'No API token provided (use --token or set GITEA_TOKEN env var)');
|
$this->log('ERROR', 'No API token provided (use --token or set GIT_TOKEN env var)');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ class JoomlaMetadataValidateCli extends CliFramework
|
|||||||
|
|
||||||
if ($httpCode === 404) {
|
if ($httpCode === 404) {
|
||||||
$this->log('ERROR', "API endpoint not found: {$url}");
|
$this->log('ERROR', "API endpoint not found: {$url}");
|
||||||
$this->log('ERROR', 'Server may need MokoGitea-Fork >= #650 (metadata endpoint rename)');
|
$this->log('ERROR', 'Server may need MokoGIT-Fork >= #650 (metadata endpoint rename)');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +328,7 @@ class JoomlaMetadataValidateCli extends CliFramework
|
|||||||
// =================================================================
|
// =================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize extension_type — map MokoGitea types to Joomla types.
|
* Normalize extension_type — map MokoGIT types to Joomla types.
|
||||||
*/
|
*/
|
||||||
private function normalizeExtensionType(string $type): string
|
private function normalizeExtensionType(string $type): string
|
||||||
{
|
{
|
||||||
@@ -340,7 +340,7 @@ class JoomlaMetadataValidateCli extends CliFramework
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Derive the Joomla element name from type + name.
|
* Derive the Joomla element name from type + name.
|
||||||
* Replicates MokoGitea's cleanJoomlaElement() + prefix logic.
|
* Replicates MokoGIT's cleanJoomlaElement() + prefix logic.
|
||||||
*/
|
*/
|
||||||
private function deriveElement(string $type, string $name): string
|
private function deriveElement(string $type, string $name): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use MokoCli\{ApiClient, AuditLogger, CliFramework, Config, PlatformAdapterFactor
|
|||||||
/**
|
/**
|
||||||
* Joomla Release Manager
|
* Joomla Release Manager
|
||||||
*
|
*
|
||||||
* Creates and manages Joomla extension releases on Gitea, including
|
* Creates and manages Joomla extension releases on Git, including
|
||||||
* package building, asset upload, and update stream management.
|
* package building, asset upload, and update stream management.
|
||||||
*
|
*
|
||||||
* @since 04.06.00
|
* @since 04.06.00
|
||||||
@@ -425,9 +425,9 @@ class JoomlaRelease extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
$releaseConfig = Config::load();
|
$releaseConfig = Config::load();
|
||||||
$releasePlatform = $releaseConfig->getString('platform', 'gitea');
|
$releasePlatform = $releaseConfig->getString('platform', 'git');
|
||||||
$releaseToken = $releasePlatform === 'gitea'
|
$releaseToken = $releasePlatform === 'git'
|
||||||
? $releaseConfig->getString('gitea.token', '')
|
? $releaseConfig->getString('git.token', '')
|
||||||
: $releaseConfig->getString('github.token', '');
|
: $releaseConfig->getString('github.token', '');
|
||||||
$acceptHeader = $releasePlatform === 'github' ? 'application/vnd.github+json' : 'application/json';
|
$acceptHeader = $releasePlatform === 'github' ? 'application/vnd.github+json' : 'application/json';
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
@@ -512,12 +512,12 @@ class JoomlaRelease extends CliFramework
|
|||||||
$this->rmdir($tmpDir);
|
$this->rmdir($tmpDir);
|
||||||
}
|
}
|
||||||
$config = Config::load();
|
$config = Config::load();
|
||||||
$platform = $config->getString('platform', 'gitea');
|
$platform = $config->getString('platform', 'git');
|
||||||
$token = $platform === 'gitea'
|
$token = $platform === 'git'
|
||||||
? $config->getString('gitea.token', '')
|
? $config->getString('git.token', '')
|
||||||
: $config->getString('github.token', '');
|
: $config->getString('github.token', '');
|
||||||
$cloneHost = $platform === 'gitea'
|
$cloneHost = $platform === 'git'
|
||||||
? rtrim($config->getString('gitea.url', 'https://git.mokoconsulting.tech'), '/')
|
? rtrim($config->getString('git.url', 'https://git.mokoconsulting.tech'), '/')
|
||||||
: 'https://github.com';
|
: 'https://github.com';
|
||||||
$url = "https://x-access-token:{$token}@" . preg_replace('#^https?://#', '', $cloneHost) . '/' . self::ORG . "/{$repo}.git";
|
$url = "https://x-access-token:{$token}@" . preg_replace('#^https?://#', '', $cloneHost) . '/' . self::ORG . "/{$repo}.git";
|
||||||
$cmd = ['git', 'clone', '--depth', '1', '--quiet', $url, $tmpDir];
|
$cmd = ['git', 'clone', '--depth', '1', '--quiet', $url, $tmpDir];
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/license_manage.php
|
* PATH: /cli/license_manage.php
|
||||||
* BRIEF: Manage license packages and keys via MokoGitea licensing API
|
* BRIEF: Manage license packages and keys via MokoGIT licensing API
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* php bin/moko license:list --org MokoConsulting
|
* php bin/moko license:list --org MokoConsulting
|
||||||
@@ -38,9 +38,9 @@ class LicenseManage extends CliFramework
|
|||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Manage license packages and keys via MokoGitea licensing API');
|
$this->setDescription('Manage license packages and keys via MokoGIT licensing API');
|
||||||
$this->addArgument('--org', 'Organization name', '');
|
$this->addArgument('--org', 'Organization name', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', '');
|
$this->addArgument('--api-base', 'Git API base URL', '');
|
||||||
$this->addArgument('--token', 'API token (or set GH_TOKEN env)', '');
|
$this->addArgument('--token', 'API token (or set GH_TOKEN env)', '');
|
||||||
|
|
||||||
// Package args
|
// Package args
|
||||||
@@ -71,7 +71,7 @@ class LicenseManage extends CliFramework
|
|||||||
{
|
{
|
||||||
// Resolve API base
|
// Resolve API base
|
||||||
$this->apiBase = $this->getArgument('--api-base')
|
$this->apiBase = $this->getArgument('--api-base')
|
||||||
?: getenv('GITEA_URL')
|
?: getenv('GIT_URL')
|
||||||
?: 'https://git.mokoconsulting.tech';
|
?: 'https://git.mokoconsulting.tech';
|
||||||
$this->apiBase = rtrim($this->apiBase, '/');
|
$this->apiBase = rtrim($this->apiBase, '/');
|
||||||
|
|
||||||
@@ -657,7 +657,7 @@ HELP;
|
|||||||
|
|
||||||
if ($httpCode === 404) {
|
if ($httpCode === 404) {
|
||||||
$this->log("API endpoint not found: {$path}", 'ERROR');
|
$this->log("API endpoint not found: {$path}", 'ERROR');
|
||||||
$this->log('The licensing API may not be deployed yet. Check MokoGitea version.', 'WARN');
|
$this->log('The licensing API may not be deployed yet. Check MokoGIT version.', 'WARN');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/manifest_detect.php
|
* PATH: /cli/manifest_detect.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Auto-detect manifest fields from source files and optionally push to API
|
* BRIEF: Auto-detect manifest fields from source files and optionally push to API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -29,10 +29,10 @@ class ManifestDetectCli extends CliFramework
|
|||||||
$this->addArgument('--json', 'Output as JSON', false);
|
$this->addArgument('--json', 'Output as JSON', false);
|
||||||
$this->addArgument('--diff', 'Show diff against current manifest API values', false);
|
$this->addArgument('--diff', 'Show diff against current manifest API values', false);
|
||||||
$this->addArgument('--update', 'Push detected fields to manifest API', false);
|
$this->addArgument('--update', 'Push detected fields to manifest API', false);
|
||||||
$this->addArgument('--token', 'Gitea API token (or GITEA_TOKEN env)', '');
|
$this->addArgument('--token', 'Git API token (or GIT_TOKEN env)', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
$this->addArgument('--api-base', 'Git API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
||||||
$this->addArgument('--org', 'Gitea org', 'MokoConsulting');
|
$this->addArgument('--org', 'Git org', 'MokoConsulting');
|
||||||
$this->addArgument('--repo', 'Gitea repo name (auto-detected from remote if empty)', '');
|
$this->addArgument('--repo', 'Git repo name (auto-detected from remote if empty)', '');
|
||||||
$this->addArgument('--github-output', 'Append fields to $GITHUB_OUTPUT', false);
|
$this->addArgument('--github-output', 'Append fields to $GITHUB_OUTPUT', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class ManifestDetectCli extends CliFramework
|
|||||||
$diffMode = (bool) $this->getArgument('--diff');
|
$diffMode = (bool) $this->getArgument('--diff');
|
||||||
$updateMode = (bool) $this->getArgument('--update');
|
$updateMode = (bool) $this->getArgument('--update');
|
||||||
$ghOutput = (bool) $this->getArgument('--github-output');
|
$ghOutput = (bool) $this->getArgument('--github-output');
|
||||||
$token = $this->getArgument('--token') ?: getenv('GITEA_TOKEN') ?: '';
|
$token = $this->getArgument('--token') ?: getenv('GIT_TOKEN') ?: '';
|
||||||
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
||||||
$org = $this->getArgument('--org');
|
$org = $this->getArgument('--org');
|
||||||
$repoName = $this->getArgument('--repo');
|
$repoName = $this->getArgument('--repo');
|
||||||
@@ -74,7 +74,7 @@ class ManifestDetectCli extends CliFramework
|
|||||||
// ── Output ──────────────────────────────────────────────────
|
// ── Output ──────────────────────────────────────────────────
|
||||||
if ($diffMode || $updateMode) {
|
if ($diffMode || $updateMode) {
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$this->log('ERROR', 'API token required for --diff/--update (use --token or GITEA_TOKEN env)');
|
$this->log('ERROR', 'API token required for --diff/--update (use --token or GIT_TOKEN env)');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if ($repoName === '') {
|
if ($repoName === '') {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ManifestElementCli extends CliFramework
|
|||||||
$githubOutput = (bool) $this->getArgument('--github-output');
|
$githubOutput = (bool) $this->getArgument('--github-output');
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
$platform = 'generic';
|
$platform = 'generic';
|
||||||
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
$manifestXml = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($manifestXml)) {
|
if (file_exists($manifestXml)) {
|
||||||
$content = file_get_contents($manifestXml);
|
$content = file_get_contents($manifestXml);
|
||||||
if (preg_match('/<platform>([^<]+)<\/platform>/', $content, $pm)) {
|
if (preg_match('/<platform>([^<]+)<\/platform>/', $content, $pm)) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/manifest_integrity.php
|
* PATH: /cli/manifest_integrity.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Cross-check manifest API fields against repo contents across the org
|
* BRIEF: Cross-check manifest API fields against repo contents across the org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -26,10 +26,10 @@ class ManifestIntegrityCli extends CliFramework
|
|||||||
{
|
{
|
||||||
$this->setDescription('Cross-check manifest fields against repo contents across the org');
|
$this->setDescription('Cross-check manifest fields against repo contents across the org');
|
||||||
$this->addArgument('--path', 'Single repo path (local mode)', '');
|
$this->addArgument('--path', 'Single repo path (local mode)', '');
|
||||||
$this->addArgument('--org', 'Gitea org (bulk mode)', 'MokoConsulting');
|
$this->addArgument('--org', 'Git org (bulk mode)', 'MokoConsulting');
|
||||||
$this->addArgument('--repo', 'Single repo name (remote mode)', '');
|
$this->addArgument('--repo', 'Single repo name (remote mode)', '');
|
||||||
$this->addArgument('--token', 'Gitea API token (or GITEA_TOKEN env)', '');
|
$this->addArgument('--token', 'Git API token (or GIT_TOKEN env)', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
$this->addArgument('--api-base', 'Git API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
||||||
$this->addArgument('--fix', 'Push fixes for detected drift', false);
|
$this->addArgument('--fix', 'Push fixes for detected drift', false);
|
||||||
$this->addArgument('--json', 'Output as JSON', false);
|
$this->addArgument('--json', 'Output as JSON', false);
|
||||||
$this->addArgument('--quiet', 'Only show repos with issues', false);
|
$this->addArgument('--quiet', 'Only show repos with issues', false);
|
||||||
@@ -40,14 +40,14 @@ class ManifestIntegrityCli extends CliFramework
|
|||||||
$path = $this->getArgument('--path');
|
$path = $this->getArgument('--path');
|
||||||
$org = $this->getArgument('--org');
|
$org = $this->getArgument('--org');
|
||||||
$repoName = $this->getArgument('--repo');
|
$repoName = $this->getArgument('--repo');
|
||||||
$token = $this->getArgument('--token') ?: getenv('GITEA_TOKEN') ?: '';
|
$token = $this->getArgument('--token') ?: getenv('GIT_TOKEN') ?: '';
|
||||||
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
||||||
$fixMode = (bool) $this->getArgument('--fix');
|
$fixMode = (bool) $this->getArgument('--fix');
|
||||||
$jsonMode = (bool) $this->getArgument('--json');
|
$jsonMode = (bool) $this->getArgument('--json');
|
||||||
$quiet = (bool) $this->getArgument('--quiet');
|
$quiet = (bool) $this->getArgument('--quiet');
|
||||||
|
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$this->log('ERROR', 'API token required (use --token or GITEA_TOKEN env)');
|
$this->log('ERROR', 'API token required (use --token or GIT_TOKEN env)');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/manifest_licensing.php
|
* PATH: /cli/manifest_licensing.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests
|
* BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
|||||||
use MokoCli\{CliFramework, SourceResolver};
|
use MokoCli\{CliFramework, SourceResolver};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the <licensing> block from .mokogitea/manifest.xml and ensures that the
|
* Reads the <licensing> block from .mokogit/manifest.xml and ensures that the
|
||||||
* Joomla extension manifest contains the correct <updateservers> and <dlid> tags.
|
* Joomla extension manifest contains the correct <updateservers> and <dlid> tags.
|
||||||
*
|
*
|
||||||
* manifest.xml licensing block example:
|
* manifest.xml licensing block example:
|
||||||
@@ -53,7 +53,7 @@ class ManifestLicensingCli extends CliFramework
|
|||||||
$ghOutput = (bool) $this->getArgument('--github-output');
|
$ghOutput = (bool) $this->getArgument('--github-output');
|
||||||
|
|
||||||
// ── 1. Read manifest.xml ──────────────────────────────────────────
|
// ── 1. Read manifest.xml ──────────────────────────────────────────
|
||||||
$manifestFile = "{$root}/.mokogitea/manifest.xml";
|
$manifestFile = "{$root}/.mokogit/manifest.xml";
|
||||||
|
|
||||||
if (!file_exists($manifestFile)) {
|
if (!file_exists($manifestFile)) {
|
||||||
$this->log('WARN', "No manifest.xml found at {$manifestFile}");
|
$this->log('WARN', "No manifest.xml found at {$manifestFile}");
|
||||||
|
|||||||
+10
-10
@@ -10,8 +10,8 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/manifest_read.php
|
* PATH: /cli/manifest_read.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Read repo metadata from Gitea manifest API, auto-detect the rest
|
* BRIEF: Read repo metadata from Git manifest API, auto-detect the rest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -27,7 +27,7 @@ class ManifestReadCli extends CliFramework
|
|||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Read repo metadata from Gitea API with auto-detection fallback');
|
$this->setDescription('Read repo metadata from Git API with auto-detection fallback');
|
||||||
$this->addArgument('--path', 'Repository root path', '.');
|
$this->addArgument('--path', 'Repository root path', '.');
|
||||||
$this->addArgument('--field', 'Single field name to output', '');
|
$this->addArgument('--field', 'Single field name to output', '');
|
||||||
$this->addArgument('--all', 'Print all fields as KEY=VALUE lines', false);
|
$this->addArgument('--all', 'Print all fields as KEY=VALUE lines', false);
|
||||||
@@ -55,7 +55,7 @@ class ManifestReadCli extends CliFramework
|
|||||||
// ── 1. Resolve org/repo ──────────────────────────────────────────
|
// ── 1. Resolve org/repo ──────────────────────────────────────────
|
||||||
[$org, $repo] = $this->resolveOrgRepo($root);
|
[$org, $repo] = $this->resolveOrgRepo($root);
|
||||||
|
|
||||||
// ── 2. Primary: Gitea manifest API ───────────────────────────────
|
// ── 2. Primary: Git manifest API ───────────────────────────────
|
||||||
$fields = null;
|
$fields = null;
|
||||||
if ($org !== '' && $repo !== '') {
|
if ($org !== '' && $repo !== '') {
|
||||||
$fields = $this->fetchFromApi($org, $repo);
|
$fields = $this->fetchFromApi($org, $repo);
|
||||||
@@ -82,12 +82,12 @@ class ManifestReadCli extends CliFramework
|
|||||||
return $this->outputFields($fields, $mode, $field);
|
return $this->outputFields($fields, $mode, $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Gitea manifest API ───────────────────────────────────────────────
|
// ── Git manifest API ───────────────────────────────────────────────
|
||||||
|
|
||||||
private function fetchFromApi(string $org, string $repo): ?array
|
private function fetchFromApi(string $org, string $repo): ?array
|
||||||
{
|
{
|
||||||
$token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: '';
|
$token = getenv('GA_TOKEN') ?: getenv('GIT_TOKEN') ?: '';
|
||||||
$baseUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
$baseUrl = getenv('GIT_URL') ?: 'https://git.mokoconsulting.tech';
|
||||||
$baseUrl = rtrim($baseUrl, '/');
|
$baseUrl = rtrim($baseUrl, '/');
|
||||||
|
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
@@ -123,7 +123,7 @@ class ManifestReadCli extends CliFramework
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->log('INFO', "Loaded metadata from Gitea manifest API ({$org}/{$repo})");
|
$this->log('INFO', "Loaded metadata from Git manifest API ({$org}/{$repo})");
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +370,7 @@ class ManifestReadCli extends CliFramework
|
|||||||
*/
|
*/
|
||||||
private function resolveOrgRepo(string $root): array
|
private function resolveOrgRepo(string $root): array
|
||||||
{
|
{
|
||||||
// 1. GITHUB_REPOSITORY env (set in Gitea Actions / GitHub Actions)
|
// 1. GITHUB_REPOSITORY env (set in Git Actions / GitHub Actions)
|
||||||
$envRepo = getenv('GITHUB_REPOSITORY') ?: '';
|
$envRepo = getenv('GITHUB_REPOSITORY') ?: '';
|
||||||
if ($envRepo !== '' && str_contains($envRepo, '/')) {
|
if ($envRepo !== '' && str_contains($envRepo, '/')) {
|
||||||
return explode('/', $envRepo, 2);
|
return explode('/', $envRepo, 2);
|
||||||
@@ -447,7 +447,7 @@ class ManifestReadCli extends CliFramework
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'github-output':
|
case 'github-output':
|
||||||
$outputFile = getenv('GITHUB_OUTPUT') ?: getenv('GITEA_OUTPUT') ?: '';
|
$outputFile = getenv('GITHUB_OUTPUT') ?: getenv('GIT_OUTPUT') ?: '';
|
||||||
$lines = [];
|
$lines = [];
|
||||||
foreach ($fields as $k => $v) {
|
foreach ($fields as $k => $v) {
|
||||||
$envKey = str_replace('-', '_', $k);
|
$envKey = str_replace('-', '_', $k);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/platform_detect.php
|
* PATH: /cli/platform_detect.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Auto-detect repository platform type and optionally update manifest
|
* BRIEF: Auto-detect repository platform type and optionally update manifest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -26,8 +26,8 @@ class PlatformDetectCli extends CliFramework
|
|||||||
{
|
{
|
||||||
$this->setDescription('Auto-detect repository platform type and optionally update manifest');
|
$this->setDescription('Auto-detect repository platform type and optionally update manifest');
|
||||||
$this->addArgument('--path', 'Local repo path to scan (default: .)', '.');
|
$this->addArgument('--path', 'Local repo path to scan (default: .)', '.');
|
||||||
$this->addArgument('--token', 'Gitea API token for updating manifest', '');
|
$this->addArgument('--token', 'Git API token for updating manifest', '');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
$this->addArgument('--git-url', 'Git URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
||||||
$this->addArgument('--owner', 'Repo owner for API update', '');
|
$this->addArgument('--owner', 'Repo owner for API update', '');
|
||||||
$this->addArgument('--repo', 'Repo name for API update', '');
|
$this->addArgument('--repo', 'Repo name for API update', '');
|
||||||
$this->addArgument('--update', 'Update manifest.platform via API (flag)', 'false');
|
$this->addArgument('--update', 'Update manifest.platform via API (flag)', 'false');
|
||||||
@@ -40,7 +40,7 @@ class PlatformDetectCli extends CliFramework
|
|||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
$token = $this->getArgument('--token');
|
$token = $this->getArgument('--token');
|
||||||
$giteaUrl = rtrim($this->getArgument('--gitea-url'), '/');
|
$gitUrl = rtrim($this->getArgument('--git-url'), '/');
|
||||||
$owner = $this->getArgument('--owner');
|
$owner = $this->getArgument('--owner');
|
||||||
$repo = $this->getArgument('--repo');
|
$repo = $this->getArgument('--repo');
|
||||||
$doUpdate = $this->isFlagSet('--update');
|
$doUpdate = $this->isFlagSet('--update');
|
||||||
@@ -79,7 +79,7 @@ class PlatformDetectCli extends CliFramework
|
|||||||
$this->log('INFO', "Updating manifest.platform for {$owner}/{$repo} to \"{$platform}\"...");
|
$this->log('INFO', "Updating manifest.platform for {$owner}/{$repo} to \"{$platform}\"...");
|
||||||
|
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'PATCH',
|
'PATCH',
|
||||||
"/api/v1/repos/{$owner}/{$repo}/metadata",
|
"/api/v1/repos/{$owner}/{$repo}/metadata",
|
||||||
@@ -136,7 +136,7 @@ class PlatformDetectCli extends CliFramework
|
|||||||
|
|
||||||
// 5. Platform — is mokocli itself or org-config
|
// 5. Platform — is mokocli itself or org-config
|
||||||
$repoName = basename($root);
|
$repoName = basename($root);
|
||||||
if (in_array($repoName, ['mokocli', 'mokogitea-org-config'])) {
|
if (in_array($repoName, ['mokocli', 'mokogit-org-config'])) {
|
||||||
return 'platform';
|
return 'platform';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,13 +152,13 @@ class PlatformDetectCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function apiRequest(
|
private function apiRequest(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $method,
|
string $method,
|
||||||
string $endpoint,
|
string $endpoint,
|
||||||
?string $body = null
|
?string $body = null
|
||||||
): array {
|
): array {
|
||||||
$url = $giteaUrl . $endpoint;
|
$url = $gitUrl . $endpoint;
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|||||||
+2
-3
@@ -37,10 +37,9 @@ class ReleaseCli extends CliFramework
|
|||||||
$repoRoot = dirname(__DIR__, 2);
|
$repoRoot = dirname(__DIR__, 2);
|
||||||
$syncFile = "{$repoRoot}/lib/Enterprise/RepositorySynchronizer.php";
|
$syncFile = "{$repoRoot}/lib/Enterprise/RepositorySynchronizer.php";
|
||||||
// Check both workflow directories for the bulk-repo-sync workflow
|
// Check both workflow directories for the bulk-repo-sync workflow
|
||||||
$bulkSyncFile = file_exists("{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml")
|
$bulkSyncFile = file_exists("{$repoRoot}/.mokogit/workflows/bulk-repo-sync.yml")
|
||||||
? "{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml"
|
? "{$repoRoot}/.mokogit/workflows/bulk-repo-sync.yml"
|
||||||
: "{$repoRoot}/.github/workflows/bulk-repo-sync.yml";
|
: "{$repoRoot}/.github/workflows/bulk-repo-sync.yml";
|
||||||
$cleanupFile = "{$repoRoot}/templates/workflows/shared/repository-cleanup.yml.template";
|
|
||||||
|
|
||||||
// -- Step 1: Read current version --
|
// -- Step 1: Read current version --
|
||||||
$readme = "{$repoRoot}/README.md";
|
$readme = "{$repoRoot}/README.md";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_body_update.php
|
* PATH: /cli/release_body_update.php
|
||||||
* BRIEF: Update Gitea release body with changelog extract and checksums
|
* BRIEF: Update Git release body with changelog extract and checksums
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,12 +23,12 @@ class ReleaseBodyUpdateCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Update Gitea release body with changelog extract and checksums');
|
$this->setDescription('Update Git release body with changelog extract and checksums');
|
||||||
$this->addArgument('--path', 'Repo root for CHANGELOG.md', '.');
|
$this->addArgument('--path', 'Repo root for CHANGELOG.md', '.');
|
||||||
$this->addArgument('--version', 'Version string', '');
|
$this->addArgument('--version', 'Version string', '');
|
||||||
$this->addArgument('--release-tag', 'Gitea release tag', '');
|
$this->addArgument('--release-tag', 'Git release tag', '');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', '');
|
$this->addArgument('--api-base', 'Git API base URL', '');
|
||||||
$this->addArgument('--zip-name', 'ZIP filename for checksum table', '');
|
$this->addArgument('--zip-name', 'ZIP filename for checksum table', '');
|
||||||
$this->addArgument('--tar-name', 'tar.gz filename for checksum table', '');
|
$this->addArgument('--tar-name', 'tar.gz filename for checksum table', '');
|
||||||
$this->addArgument('--zip-sha', 'SHA256 of ZIP', '');
|
$this->addArgument('--zip-sha', 'SHA256 of ZIP', '');
|
||||||
@@ -50,7 +50,7 @@ class ReleaseBodyUpdateCli extends CliFramework
|
|||||||
$outputSummary = $this->getArgument('--output-summary');
|
$outputSummary = $this->getArgument('--output-summary');
|
||||||
|
|
||||||
if (empty($token)) {
|
if (empty($token)) {
|
||||||
$token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: '';
|
$token = getenv('MOKOGIT_TOKEN') ?: getenv('GIT_TOKEN') ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($version) || empty($releaseTag) || empty($token) || empty($apiBase)) {
|
if (empty($version) || empty($releaseTag) || empty($token) || empty($apiBase)) {
|
||||||
|
|||||||
+12
-12
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_cascade.php
|
* PATH: /cli/release_cascade.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Cascade release zip to all lower stability channels
|
* BRIEF: Cascade release zip to all lower stability channels
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -40,8 +40,8 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
{
|
{
|
||||||
$this->setDescription('Cascade release zip to all lower stability channels');
|
$this->setDescription('Cascade release zip to all lower stability channels');
|
||||||
$this->addArgument('--stability', 'Source stability channel (required)', '');
|
$this->addArgument('--stability', 'Source stability channel (required)', '');
|
||||||
$this->addArgument('--token', 'Gitea API token (required)', '');
|
$this->addArgument('--token', 'Git API token (required)', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL for the repo (required)', '');
|
$this->addArgument('--api-base', 'Git API base URL for the repo (required)', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
@@ -51,9 +51,9 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
||||||
|
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$envToken = getenv('MOKOGITEA_TOKEN');
|
$envToken = getenv('MOKOGIT_TOKEN');
|
||||||
if ($envToken === false || $envToken === '') {
|
if ($envToken === false || $envToken === '') {
|
||||||
$envToken = getenv('GITEA_TOKEN');
|
$envToken = getenv('GIT_TOKEN');
|
||||||
}
|
}
|
||||||
if ($envToken !== false && $envToken !== '') {
|
if ($envToken !== false && $envToken !== '') {
|
||||||
$token = $envToken;
|
$token = $envToken;
|
||||||
@@ -85,7 +85,7 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Get source release
|
// 1. Get source release
|
||||||
$sourceRelease = $this->giteaApi("{$apiBase}/releases/tags/{$sourceTag}", $token);
|
$sourceRelease = $this->gitApi("{$apiBase}/releases/tags/{$sourceTag}", $token);
|
||||||
if ($sourceRelease === null) {
|
if ($sourceRelease === null) {
|
||||||
$this->log('WARN', "No release found at tag '{$sourceTag}' — nothing to cascade.");
|
$this->log('WARN', "No release found at tag '{$sourceTag}' — nothing to cascade.");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -179,7 +179,7 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find existing release at target tag
|
// Find existing release at target tag
|
||||||
$existing = $this->giteaApi("{$apiBase}/releases/tags/{$targetTag}", $token);
|
$existing = $this->gitApi("{$apiBase}/releases/tags/{$targetTag}", $token);
|
||||||
|
|
||||||
if ($existing !== null && !empty($existing['id'])) {
|
if ($existing !== null && !empty($existing['id'])) {
|
||||||
$releaseId = (int) $existing['id'];
|
$releaseId = (int) $existing['id'];
|
||||||
@@ -189,7 +189,7 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
foreach ($existingAssets as $asset) {
|
foreach ($existingAssets as $asset) {
|
||||||
$assetId = $asset['id'] ?? 0;
|
$assetId = $asset['id'] ?? 0;
|
||||||
if ($assetId > 0) {
|
if ($assetId > 0) {
|
||||||
$this->giteaApi(
|
$this->gitApi(
|
||||||
"{$apiBase}/releases/{$releaseId}/assets/{$assetId}",
|
"{$apiBase}/releases/{$releaseId}/assets/{$assetId}",
|
||||||
$token, 'DELETE'
|
$token, 'DELETE'
|
||||||
);
|
);
|
||||||
@@ -201,7 +201,7 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
'name' => $sourceVersion,
|
'name' => $sourceVersion,
|
||||||
'body' => $sourceBody,
|
'body' => $sourceBody,
|
||||||
]);
|
]);
|
||||||
$this->giteaApi(
|
$this->gitApi(
|
||||||
"{$apiBase}/releases/{$releaseId}",
|
"{$apiBase}/releases/{$releaseId}",
|
||||||
$token, 'PATCH', $updatePayload
|
$token, 'PATCH', $updatePayload
|
||||||
);
|
);
|
||||||
@@ -217,7 +217,7 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
'body' => $sourceBody,
|
'body' => $sourceBody,
|
||||||
'prerelease' => ($targetTag !== 'stable'),
|
'prerelease' => ($targetTag !== 'stable'),
|
||||||
]);
|
]);
|
||||||
$newRelease = $this->giteaApi("{$apiBase}/releases", $token, 'POST', $createPayload);
|
$newRelease = $this->gitApi("{$apiBase}/releases", $token, 'POST', $createPayload);
|
||||||
if ($newRelease === null || empty($newRelease['id'])) {
|
if ($newRelease === null || empty($newRelease['id'])) {
|
||||||
$this->log('ERROR', " Failed to create release at tag '{$targetTag}'");
|
$this->log('ERROR', " Failed to create release at tag '{$targetTag}'");
|
||||||
return false;
|
return false;
|
||||||
@@ -302,9 +302,9 @@ class ReleaseCascadeCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an HTTP request to the Gitea API.
|
* Make an HTTP request to the Git API.
|
||||||
*/
|
*/
|
||||||
private function giteaApi(
|
private function gitApi(
|
||||||
string $url,
|
string $url,
|
||||||
string $token,
|
string $token,
|
||||||
string $method = 'GET',
|
string $method = 'GET',
|
||||||
|
|||||||
+13
-13
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_create.php
|
* PATH: /cli/release_create.php
|
||||||
* BRIEF: Create or overwrite a Gitea release with proper naming
|
* BRIEF: Create or overwrite a Git release with proper naming
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,12 +23,12 @@ class ReleaseCreateCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Create or overwrite a Gitea release with proper naming');
|
$this->setDescription('Create or overwrite a Git release with proper naming');
|
||||||
$this->addArgument('--path', 'Repo root for manifest detection (default: .)', '.');
|
$this->addArgument('--path', 'Repo root for manifest detection (default: .)', '.');
|
||||||
$this->addArgument('--version', 'Version string (required)', '');
|
$this->addArgument('--version', 'Version string (required)', '');
|
||||||
$this->addArgument('--tag', 'Release tag name (required)', '');
|
$this->addArgument('--tag', 'Release tag name (required)', '');
|
||||||
$this->addArgument('--token', 'Gitea API token (required)', '');
|
$this->addArgument('--token', 'Git API token (required)', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL for the repo (required)', '');
|
$this->addArgument('--api-base', 'Git API base URL for the repo (required)', '');
|
||||||
$this->addArgument('--branch', 'Target commitish (default: main)', 'main');
|
$this->addArgument('--branch', 'Target commitish (default: main)', 'main');
|
||||||
$this->addArgument('--repo', 'Repo name for fallback element detection', '');
|
$this->addArgument('--repo', 'Repo name for fallback element detection', '');
|
||||||
$this->addArgument('--prerelease', 'Mark release as prerelease', false);
|
$this->addArgument('--prerelease', 'Mark release as prerelease', false);
|
||||||
@@ -47,9 +47,9 @@ class ReleaseCreateCli extends CliFramework
|
|||||||
|
|
||||||
// Allow token from environment
|
// Allow token from environment
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$envToken = getenv('MOKOGITEA_TOKEN');
|
$envToken = getenv('MOKOGIT_TOKEN');
|
||||||
if ($envToken === false || $envToken === '') {
|
if ($envToken === false || $envToken === '') {
|
||||||
$envToken = getenv('GITEA_TOKEN');
|
$envToken = getenv('GIT_TOKEN');
|
||||||
}
|
}
|
||||||
if ($envToken !== false && $envToken !== '') {
|
if ($envToken !== false && $envToken !== '') {
|
||||||
$token = $envToken;
|
$token = $envToken;
|
||||||
@@ -62,7 +62,7 @@ class ReleaseCreateCli extends CliFramework
|
|||||||
$this->log('ERROR', " --branch main Target commitish (default: main)");
|
$this->log('ERROR', " --branch main Target commitish (default: main)");
|
||||||
$this->log('ERROR', " --repo REPO Repo name for fallback element detection");
|
$this->log('ERROR', " --repo REPO Repo name for fallback element detection");
|
||||||
$this->log('ERROR', " --prerelease Mark release as prerelease");
|
$this->log('ERROR', " --prerelease Mark release as prerelease");
|
||||||
$this->log('ERROR', " Token can also be set via MOKOGITEA_TOKEN or GITEA_TOKEN env var");
|
$this->log('ERROR', " Token can also be set via MOKOGIT_TOKEN or GIT_TOKEN env var");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ class ReleaseCreateCli extends CliFramework
|
|||||||
// Detect platform and display name from manifest.xml
|
// Detect platform and display name from manifest.xml
|
||||||
$platform = 'generic';
|
$platform = 'generic';
|
||||||
$prettyName = '';
|
$prettyName = '';
|
||||||
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
$manifestXml = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($manifestXml)) {
|
if (file_exists($manifestXml)) {
|
||||||
$content = file_get_contents($manifestXml);
|
$content = file_get_contents($manifestXml);
|
||||||
if ($content !== false) {
|
if ($content !== false) {
|
||||||
@@ -238,16 +238,16 @@ class ReleaseCreateCli extends CliFramework
|
|||||||
|
|
||||||
// ── Delete existing release at tag (if present) ─────────────────────────────
|
// ── Delete existing release at tag (if present) ─────────────────────────────
|
||||||
|
|
||||||
$existing = $this->giteaApi("{$apiBase}/releases/tags/{$tag}", $token);
|
$existing = $this->gitApi("{$apiBase}/releases/tags/{$tag}", $token);
|
||||||
if ($existing !== null && !empty($existing['id'])) {
|
if ($existing !== null && !empty($existing['id'])) {
|
||||||
$existingId = $existing['id'];
|
$existingId = $existing['id'];
|
||||||
echo "Deleting existing release: {$tag} (id: {$existingId})\n";
|
echo "Deleting existing release: {$tag} (id: {$existingId})\n";
|
||||||
|
|
||||||
// Delete release
|
// Delete release
|
||||||
$this->giteaApi("{$apiBase}/releases/{$existingId}", $token, 'DELETE');
|
$this->gitApi("{$apiBase}/releases/{$existingId}", $token, 'DELETE');
|
||||||
|
|
||||||
// Delete tag
|
// Delete tag
|
||||||
$this->giteaApi("{$apiBase}/tags/{$tag}", $token, 'DELETE');
|
$this->gitApi("{$apiBase}/tags/{$tag}", $token, 'DELETE');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Create new release ──────────────────────────────────────────────────────
|
// ── Create new release ──────────────────────────────────────────────────────
|
||||||
@@ -260,7 +260,7 @@ class ReleaseCreateCli extends CliFramework
|
|||||||
'prerelease' => $prerelease,
|
'prerelease' => $prerelease,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$newRelease = $this->giteaApi("{$apiBase}/releases", $token, 'POST', $payload !== false ? $payload : '{}');
|
$newRelease = $this->gitApi("{$apiBase}/releases", $token, 'POST', $payload !== false ? $payload : '{}');
|
||||||
if ($newRelease === null || empty($newRelease['id'])) {
|
if ($newRelease === null || empty($newRelease['id'])) {
|
||||||
$this->log('ERROR', "Failed to create release at tag: {$tag}");
|
$this->log('ERROR', "Failed to create release at tag: {$tag}");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -274,7 +274,7 @@ class ReleaseCreateCli extends CliFramework
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
private function gitApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
if ($ch === false) {
|
if ($ch === false) {
|
||||||
|
|||||||
+16
-16
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_manage.php
|
* PATH: /cli/release_manage.php
|
||||||
* BRIEF: Create/update Gitea releases, upload assets, update release body
|
* BRIEF: Create/update Git releases, upload assets, update release body
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,7 +23,7 @@ class ReleaseManageCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Create/update Gitea releases, upload assets, update release body');
|
$this->setDescription('Create/update Git releases, upload assets, update release body');
|
||||||
$this->addArgument('--action', 'create | upload | update-body | delete', null);
|
$this->addArgument('--action', 'create | upload | update-body | delete', null);
|
||||||
$this->addArgument('--tag', 'Release tag name', null);
|
$this->addArgument('--tag', 'Release tag name', null);
|
||||||
$this->addArgument('--name', 'Release name/title', null);
|
$this->addArgument('--name', 'Release name/title', null);
|
||||||
@@ -31,8 +31,8 @@ class ReleaseManageCli extends CliFramework
|
|||||||
$this->addArgument('--body-file', 'Read body from file', null);
|
$this->addArgument('--body-file', 'Read body from file', null);
|
||||||
$this->addArgument('--target', 'Target branch/commitish', 'main');
|
$this->addArgument('--target', 'Target branch/commitish', 'main');
|
||||||
$this->addArgument('--files', 'Comma-separated file paths to upload', null);
|
$this->addArgument('--files', 'Comma-separated file paths to upload', null);
|
||||||
$this->addArgument('--token', 'Gitea API token', null);
|
$this->addArgument('--token', 'Git API token', null);
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', null);
|
$this->addArgument('--api-base', 'Git API base URL', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
@@ -48,7 +48,7 @@ class ReleaseManageCli extends CliFramework
|
|||||||
$apiBase = $this->getArgument('--api-base');
|
$apiBase = $this->getArgument('--api-base');
|
||||||
$files = $filesArg !== null ? array_filter(explode(',', $filesArg)) : [];
|
$files = $filesArg !== null ? array_filter(explode(',', $filesArg)) : [];
|
||||||
if ($token === null) {
|
if ($token === null) {
|
||||||
$token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
$token = getenv('MOKOGIT_TOKEN') ?: getenv('GIT_TOKEN') ?: null;
|
||||||
}
|
}
|
||||||
if ($bodyFile !== null && file_exists($bodyFile)) {
|
if ($bodyFile !== null && file_exists($bodyFile)) {
|
||||||
$body = file_get_contents($bodyFile);
|
$body = file_get_contents($bodyFile);
|
||||||
@@ -62,12 +62,12 @@ class ReleaseManageCli extends CliFramework
|
|||||||
$existing = $this->getReleaseByTag($apiBase, $tag, $token);
|
$existing = $this->getReleaseByTag($apiBase, $tag, $token);
|
||||||
if ($existing !== null) {
|
if ($existing !== null) {
|
||||||
$existingId = $existing['id'];
|
$existingId = $existing['id'];
|
||||||
$this->releaseGiteaApi("{$apiBase}/releases/{$existingId}", 'DELETE', $token);
|
$this->releaseGitApi("{$apiBase}/releases/{$existingId}", 'DELETE', $token);
|
||||||
$this->releaseGiteaApi("{$apiBase}/tags/{$tag}", 'DELETE', $token);
|
$this->releaseGitApi("{$apiBase}/tags/{$tag}", 'DELETE', $token);
|
||||||
echo "Deleted previous release: {$tag} (id: {$existingId})\n";
|
echo "Deleted previous release: {$tag} (id: {$existingId})\n";
|
||||||
}
|
}
|
||||||
$payload = json_encode(['tag_name' => $tag, 'name' => $name ?? $tag, 'body' => $body ?? '', 'target_commitish' => $target]);
|
$payload = json_encode(['tag_name' => $tag, 'name' => $name ?? $tag, 'body' => $body ?? '', 'target_commitish' => $target]);
|
||||||
$result = $this->releaseGiteaApi("{$apiBase}/releases", 'POST', $token, $payload);
|
$result = $this->releaseGitApi("{$apiBase}/releases", 'POST', $token, $payload);
|
||||||
if ($result['code'] >= 200 && $result['code'] < 300) {
|
if ($result['code'] >= 200 && $result['code'] < 300) {
|
||||||
$releaseId = $result['data']['id'] ?? 'unknown';
|
$releaseId = $result['data']['id'] ?? 'unknown';
|
||||||
echo "Release created: {$name} (tag: {$tag}, id: {$releaseId})\n";
|
echo "Release created: {$name} (tag: {$tag}, id: {$releaseId})\n";
|
||||||
@@ -87,7 +87,7 @@ class ReleaseManageCli extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
$releaseId = $release['id'];
|
$releaseId = $release['id'];
|
||||||
$assetsResult = $this->releaseGiteaApi("{$apiBase}/releases/{$releaseId}/assets", 'GET', $token);
|
$assetsResult = $this->releaseGitApi("{$apiBase}/releases/{$releaseId}/assets", 'GET', $token);
|
||||||
$existingAssets = $assetsResult['data'] ?? [];
|
$existingAssets = $assetsResult['data'] ?? [];
|
||||||
foreach ($files as $filePath) {
|
foreach ($files as $filePath) {
|
||||||
$filePath = trim($filePath);
|
$filePath = trim($filePath);
|
||||||
@@ -98,13 +98,13 @@ class ReleaseManageCli extends CliFramework
|
|||||||
$fileName = basename($filePath);
|
$fileName = basename($filePath);
|
||||||
foreach ($existingAssets as $asset) {
|
foreach ($existingAssets as $asset) {
|
||||||
if (($asset['name'] ?? '') === $fileName) {
|
if (($asset['name'] ?? '') === $fileName) {
|
||||||
$this->releaseGiteaApi("{$apiBase}/releases/{$releaseId}/assets/{$asset['id']}", 'DELETE', $token);
|
$this->releaseGitApi("{$apiBase}/releases/{$releaseId}/assets/{$asset['id']}", 'DELETE', $token);
|
||||||
echo "Deleted existing asset: {$fileName}\n";
|
echo "Deleted existing asset: {$fileName}\n";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($fileName);
|
$uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($fileName);
|
||||||
$result = $this->releaseGiteaApi($uploadUrl, 'POST', $token, null, $filePath);
|
$result = $this->releaseGitApi($uploadUrl, 'POST', $token, null, $filePath);
|
||||||
if ($result['code'] >= 200 && $result['code'] < 300) {
|
if ($result['code'] >= 200 && $result['code'] < 300) {
|
||||||
echo "Uploaded: {$fileName}\n";
|
echo "Uploaded: {$fileName}\n";
|
||||||
} else {
|
} else {
|
||||||
@@ -119,7 +119,7 @@ class ReleaseManageCli extends CliFramework
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
$payload = json_encode(['body' => $body ?? '']);
|
$payload = json_encode(['body' => $body ?? '']);
|
||||||
$result = $this->releaseGiteaApi("{$apiBase}/releases/{$release['id']}", 'PATCH', $token, $payload);
|
$result = $this->releaseGitApi("{$apiBase}/releases/{$release['id']}", 'PATCH', $token, $payload);
|
||||||
if ($result['code'] >= 200 && $result['code'] < 300) {
|
if ($result['code'] >= 200 && $result['code'] < 300) {
|
||||||
echo "Release body updated for tag: {$tag}\n";
|
echo "Release body updated for tag: {$tag}\n";
|
||||||
} else {
|
} else {
|
||||||
@@ -130,8 +130,8 @@ class ReleaseManageCli extends CliFramework
|
|||||||
case 'delete':
|
case 'delete':
|
||||||
$existing = $this->getReleaseByTag($apiBase, $tag, $token);
|
$existing = $this->getReleaseByTag($apiBase, $tag, $token);
|
||||||
if ($existing !== null) {
|
if ($existing !== null) {
|
||||||
$this->releaseGiteaApi("{$apiBase}/releases/{$existing['id']}", 'DELETE', $token);
|
$this->releaseGitApi("{$apiBase}/releases/{$existing['id']}", 'DELETE', $token);
|
||||||
$this->releaseGiteaApi("{$apiBase}/tags/{$tag}", 'DELETE', $token);
|
$this->releaseGitApi("{$apiBase}/tags/{$tag}", 'DELETE', $token);
|
||||||
echo "Deleted: {$tag} (id: {$existing['id']})\n";
|
echo "Deleted: {$tag} (id: {$existing['id']})\n";
|
||||||
} else {
|
} else {
|
||||||
echo "No release found for tag: {$tag}\n";
|
echo "No release found for tag: {$tag}\n";
|
||||||
@@ -144,7 +144,7 @@ class ReleaseManageCli extends CliFramework
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function releaseGiteaApi(string $url, string $method, string $token, ?string $jsonBody = null, ?string $filePath = null): array
|
private function releaseGitApi(string $url, string $method, string $token, ?string $jsonBody = null, ?string $filePath = null): array
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
$headers = ["Authorization: token {$token}"];
|
$headers = ["Authorization: token {$token}"];
|
||||||
@@ -166,7 +166,7 @@ class ReleaseManageCli extends CliFramework
|
|||||||
|
|
||||||
private function getReleaseByTag(string $apiBase, string $tag, string $token): ?array
|
private function getReleaseByTag(string $apiBase, string $tag, string $token): ?array
|
||||||
{
|
{
|
||||||
$result = $this->releaseGiteaApi("{$apiBase}/releases/tags/{$tag}", 'GET', $token);
|
$result = $this->releaseGitApi("{$apiBase}/releases/tags/{$tag}", 'GET', $token);
|
||||||
return ($result['code'] === 200 && isset($result['data']['id'])) ? $result['data'] : null;
|
return ($result['code'] === 200 && isset($result['data']['id'])) ? $result['data'] : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-19
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_mirror.php
|
* PATH: /cli/release_mirror.php
|
||||||
* BRIEF: Mirror a Gitea release (with assets) to a GitHub repository
|
* BRIEF: Mirror a Git release (with assets) to a GitHub repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,11 +23,11 @@ class ReleaseMirrorCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Mirror a Gitea release (with assets) to a GitHub repository');
|
$this->setDescription('Mirror a Git release (with assets) to a GitHub repository');
|
||||||
$this->addArgument('--version', 'Version string (required)', '');
|
$this->addArgument('--version', 'Version string (required)', '');
|
||||||
$this->addArgument('--tag', 'Release tag name (required)', '');
|
$this->addArgument('--tag', 'Release tag name (required)', '');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL for the repo (required)', '');
|
$this->addArgument('--api-base', 'Git API base URL for the repo (required)', '');
|
||||||
$this->addArgument('--gh-token', 'GitHub personal access token', '');
|
$this->addArgument('--gh-token', 'GitHub personal access token', '');
|
||||||
$this->addArgument('--gh-repo', 'GitHub org/repo (required)', '');
|
$this->addArgument('--gh-repo', 'GitHub org/repo (required)', '');
|
||||||
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
||||||
@@ -44,30 +44,30 @@ class ReleaseMirrorCli extends CliFramework
|
|||||||
$branch = $this->getArgument('--branch');
|
$branch = $this->getArgument('--branch');
|
||||||
|
|
||||||
// Allow tokens from environment
|
// Allow tokens from environment
|
||||||
$token = $token ?: (getenv('MOKOGITEA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: ''));
|
$token = $token ?: (getenv('MOKOGIT_TOKEN') ?: (getenv('GIT_TOKEN') ?: ''));
|
||||||
$ghToken = $ghToken ?: (getenv('GH_MIRROR_TOKEN') ?: '');
|
$ghToken = $ghToken ?: (getenv('GH_MIRROR_TOKEN') ?: '');
|
||||||
|
|
||||||
if ($version === '' || $tag === '' || $token === '' || $apiBase === '' || $ghToken === '' || $ghRepo === '') {
|
if ($version === '' || $tag === '' || $token === '' || $apiBase === '' || $ghToken === '' || $ghRepo === '') {
|
||||||
$this->log('ERROR', "Usage: release_mirror.php --version VER --tag TAG --token TOKEN " .
|
$this->log('ERROR', "Usage: release_mirror.php --version VER --tag TAG --token TOKEN " .
|
||||||
"--api-base URL --gh-token GH_MIRROR_TOKEN --gh-repo org/repo [--branch main]");
|
"--api-base URL --gh-token GH_MIRROR_TOKEN --gh-repo org/repo [--branch main]");
|
||||||
$this->log('ERROR', " --token: Gitea token (or MOKOGITEA_TOKEN / GITEA_TOKEN env)");
|
$this->log('ERROR', " --token: Git token (or MOKOGIT_TOKEN / GIT_TOKEN env)");
|
||||||
$this->log('ERROR', " --gh-token: GitHub token (or GH_MIRROR_TOKEN env)");
|
$this->log('ERROR', " --gh-token: GitHub token (or GH_MIRROR_TOKEN env)");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 1: Get Gitea release by tag ─────────────────────────────────────────
|
// ── Step 1: Get Git release by tag ─────────────────────────────────────────
|
||||||
|
|
||||||
echo "Fetching Gitea release: {$tag}\n";
|
echo "Fetching Git release: {$tag}\n";
|
||||||
$giteaRelease = $this->giteaApi("{$apiBase}/releases/tags/{$tag}", $token);
|
$gitRelease = $this->gitApi("{$apiBase}/releases/tags/{$tag}", $token);
|
||||||
if (!$giteaRelease || empty($giteaRelease['id'])) {
|
if (!$gitRelease || empty($gitRelease['id'])) {
|
||||||
$this->log('ERROR', "No Gitea release found with tag: {$tag}");
|
$this->log('ERROR', "No Git release found with tag: {$tag}");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$giteaId = $giteaRelease['id'];
|
$gitId = $gitRelease['id'];
|
||||||
$releaseName = $giteaRelease['name'] ?? "{$version}";
|
$releaseName = $gitRelease['name'] ?? "{$version}";
|
||||||
$releaseBody = $giteaRelease['body'] ?? '';
|
$releaseBody = $gitRelease['body'] ?? '';
|
||||||
$assets = $giteaRelease['assets'] ?? [];
|
$assets = $gitRelease['assets'] ?? [];
|
||||||
|
|
||||||
echo " Name: {$releaseName}\n";
|
echo " Name: {$releaseName}\n";
|
||||||
echo " Assets: " . count($assets) . " file(s)\n";
|
echo " Assets: " . count($assets) . " file(s)\n";
|
||||||
@@ -109,7 +109,7 @@ class ReleaseMirrorCli extends CliFramework
|
|||||||
echo " Created GitHub release (id: {$ghReleaseId})\n";
|
echo " Created GitHub release (id: {$ghReleaseId})\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 3: Download assets from Gitea ───────────────────────────────────────
|
// ── Step 3: Download assets from Git ───────────────────────────────────────
|
||||||
|
|
||||||
$tmpDir = sys_get_temp_dir() . '/moko-mirror-' . getmypid();
|
$tmpDir = sys_get_temp_dir() . '/moko-mirror-' . getmypid();
|
||||||
@mkdir($tmpDir, 0755, true);
|
@mkdir($tmpDir, 0755, true);
|
||||||
@@ -126,7 +126,7 @@ class ReleaseMirrorCli extends CliFramework
|
|||||||
$localPath = "{$tmpDir}/{$name}";
|
$localPath = "{$tmpDir}/{$name}";
|
||||||
echo " Downloading: {$name}\n";
|
echo " Downloading: {$name}\n";
|
||||||
|
|
||||||
if (!$this->giteaDownload($downloadUrl, $token, $localPath)) {
|
if (!$this->gitDownload($downloadUrl, $token, $localPath)) {
|
||||||
$this->log('ERROR', " Failed to download: {$name}");
|
$this->log('ERROR', " Failed to download: {$name}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ class ReleaseMirrorCli extends CliFramework
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
private function gitApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
@@ -176,7 +176,7 @@ class ReleaseMirrorCli extends CliFramework
|
|||||||
return json_decode($response, true) ?: null;
|
return json_decode($response, true) ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaDownload(string $url, string $token, string $dest): bool
|
private function gitDownload(string $url, string $token, string $dest): bool
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
$fp = fopen($dest, 'wb');
|
$fp = fopen($dest, 'wb');
|
||||||
|
|||||||
+94
-16
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_package.php
|
* PATH: /cli/release_package.php
|
||||||
* BRIEF: Build packages (ZIP + tar.gz) with SHA-256 and upload to Gitea release
|
* BRIEF: Build packages (ZIP + tar.gz) with SHA-256 and upload to Git release
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -35,12 +35,12 @@ class ReleasePackageCli extends CliFramework
|
|||||||
|
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Build packages (ZIP + tar.gz) with SHA-256 and upload to Gitea release');
|
$this->setDescription('Build packages (ZIP + tar.gz) with SHA-256 and upload to Git release');
|
||||||
$this->addArgument('--path', 'Repository root path', '.');
|
$this->addArgument('--path', 'Repository root path', '.');
|
||||||
$this->addArgument('--version', 'Release version', '');
|
$this->addArgument('--version', 'Release version', '');
|
||||||
$this->addArgument('--tag', 'Release tag name', '');
|
$this->addArgument('--tag', 'Release tag name', '');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL', '');
|
$this->addArgument('--api-base', 'Git API base URL', '');
|
||||||
$this->addArgument('--repo', 'Repo name for element detection fallback', '');
|
$this->addArgument('--repo', 'Repo name for element detection fallback', '');
|
||||||
$this->addArgument('--output', 'Output directory for built packages', '');
|
$this->addArgument('--output', 'Output directory for built packages', '');
|
||||||
}
|
}
|
||||||
@@ -61,23 +61,23 @@ class ReleasePackageCli extends CliFramework
|
|||||||
|
|
||||||
// Allow token from environment
|
// Allow token from environment
|
||||||
if ($token === '' || $token === null) {
|
if ($token === '' || $token === null) {
|
||||||
$token = getenv('MOKOGITEA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: '');
|
$token = getenv('MOKOGIT_TOKEN') ?: (getenv('GIT_TOKEN') ?: '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($version === '' || $tag === '' || $token === '' || $apiBase === '') {
|
if ($version === '' || $tag === '' || $token === '' || $apiBase === '') {
|
||||||
$this->log('ERROR', "Usage: release_package.php --path . --version VER --tag TAG --token TOKEN --api-base URL");
|
$this->log('ERROR', "Usage: release_package.php --path . --version VER --tag TAG --token TOKEN --api-base URL");
|
||||||
$this->log('ERROR', " --repo REPO Repo name for element detection fallback");
|
$this->log('ERROR', " --repo REPO Repo name for element detection fallback");
|
||||||
$this->log('ERROR', " --output DIR Output directory for built packages (default: sys_get_temp_dir())");
|
$this->log('ERROR', " --output DIR Output directory for built packages (default: sys_get_temp_dir())");
|
||||||
$this->log('ERROR', " Token can also be set via MOKOGITEA_TOKEN or GITEA_TOKEN env var");
|
$this->log('ERROR', " Token can also be set via MOKOGIT_TOKEN or GIT_TOKEN env var");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
// ── Read platform from .mokogitea/manifest.xml ───────────────────────
|
// ── Read platform from .mokogit/manifest.xml ───────────────────────
|
||||||
$detectedPlatform = 'generic';
|
$detectedPlatform = 'generic';
|
||||||
$detectedEntryPoint = '';
|
$detectedEntryPoint = '';
|
||||||
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
$mokoManifest = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($mokoManifest)) {
|
if (file_exists($mokoManifest)) {
|
||||||
$mokoXml = @simplexml_load_file($mokoManifest);
|
$mokoXml = @simplexml_load_file($mokoManifest);
|
||||||
if ($mokoXml !== false) {
|
if ($mokoXml !== false) {
|
||||||
@@ -270,12 +270,90 @@ class ReleasePackageCli extends CliFramework
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if sub-source is itself a Joomla package (nested package)
|
||||||
|
$nestedPkgManifests = glob("{$subSourceDir}/pkg_*.xml") ?: [];
|
||||||
|
$isNestedPackage = !empty($nestedPkgManifests) && is_dir("{$subSourceDir}/packages");
|
||||||
|
|
||||||
$subZip = new \ZipArchive();
|
$subZip = new \ZipArchive();
|
||||||
if ($subZip->open($subZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
if ($subZip->open($subZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||||
$this->log('ERROR', "Failed to create sub-package ZIP: {$subZipPath}");
|
$this->log('ERROR', "Failed to create sub-package ZIP: {$subZipPath}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns);
|
|
||||||
|
if ($isNestedPackage) {
|
||||||
|
// Build nested package: zip each sub-extension, then assemble
|
||||||
|
echo " Building nested package: {$subName}\n";
|
||||||
|
$nestedPkgDirs = glob("{$subSourceDir}/packages/*", GLOB_ONLYDIR) ?: [];
|
||||||
|
|
||||||
|
$nestedManifested = [];
|
||||||
|
foreach ($nestedPkgManifests as $npmf) {
|
||||||
|
$npmXml = @simplexml_load_file($npmf);
|
||||||
|
if ($npmXml && isset($npmXml->files)) {
|
||||||
|
foreach ($npmXml->files->file as $fn) {
|
||||||
|
$nzn = pathinfo((string) $fn, PATHINFO_FILENAME);
|
||||||
|
if (!empty($nzn)) {
|
||||||
|
$nestedManifested[$nzn] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a subdirectory to avoid filename collisions with outer ZIPs
|
||||||
|
$nestedOutputDir = "{$outputDir}/nested_{$subName}";
|
||||||
|
@mkdir($nestedOutputDir, 0755, true);
|
||||||
|
|
||||||
|
foreach ($nestedPkgDirs as $npd) {
|
||||||
|
$nestedSubName = basename($npd);
|
||||||
|
if (!empty($nestedManifested) && !isset($nestedManifested[$nestedSubName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$nestedSubZipPath = "{$nestedOutputDir}/{$nestedSubName}.zip";
|
||||||
|
$nsZip = new \ZipArchive();
|
||||||
|
if ($nsZip->open($nestedSubZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->addDirToZip($nsZip, $npd, '', $this->excludePatterns);
|
||||||
|
$nsZip->close();
|
||||||
|
$subZip->addFile($nestedSubZipPath, "packages/{$nestedSubName}.zip");
|
||||||
|
echo " Nested sub: {$nestedSubName}.zip\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch nested manifest: ensure folder="packages" so Joomla finds sub-ZIPs
|
||||||
|
foreach ($nestedPkgManifests as $npmf) {
|
||||||
|
$npmContent = file_get_contents($npmf);
|
||||||
|
if ($npmContent !== false
|
||||||
|
&& strpos($npmContent, '<files>') !== false
|
||||||
|
&& strpos($npmContent, 'folder="packages"') === false
|
||||||
|
) {
|
||||||
|
$npmContent = str_replace('<files>', '<files folder="packages">', $npmContent);
|
||||||
|
file_put_contents($npmf, $npmContent);
|
||||||
|
echo " Fixed: added folder=\"packages\" to " . basename($npmf) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add top-level files (manifest, script, etc.) with exclude filter
|
||||||
|
$nestedTopFiles = array_merge(
|
||||||
|
glob("{$subSourceDir}/*.xml") ?: [],
|
||||||
|
glob("{$subSourceDir}/*.php") ?: []
|
||||||
|
);
|
||||||
|
foreach ($nestedTopFiles as $ntf) {
|
||||||
|
if (!$this->isExcluded(basename($ntf), $this->excludePatterns)) {
|
||||||
|
$subZip->addFile($ntf, basename($ntf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all top-level directories except packages/
|
||||||
|
$nestedTopDirs = glob("{$subSourceDir}/*", GLOB_ONLYDIR) ?: [];
|
||||||
|
foreach ($nestedTopDirs as $ntd) {
|
||||||
|
$ndName = basename($ntd);
|
||||||
|
if ($ndName === 'packages') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->addDirToZip($subZip, $ntd, $ndName, $this->excludePatterns);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns);
|
||||||
|
}
|
||||||
$subZip->close();
|
$subZip->close();
|
||||||
|
|
||||||
$zip->addFile($subZipPath, "packages/{$subName}.zip");
|
$zip->addFile($subZipPath, "packages/{$subName}.zip");
|
||||||
@@ -380,7 +458,7 @@ class ReleasePackageCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Get release ID from tag ──────────────────────────────────────────
|
// ── Get release ID from tag ──────────────────────────────────────────
|
||||||
$result = $this->giteaApiRequest("{$apiBase}/releases/tags/{$tag}", $token);
|
$result = $this->gitApiRequest("{$apiBase}/releases/tags/{$tag}", $token);
|
||||||
if ($result['data'] === null || !isset($result['data']['id'])) {
|
if ($result['data'] === null || !isset($result['data']['id'])) {
|
||||||
$this->log('ERROR', "No release found for tag: {$tag} (HTTP {$result['code']})");
|
$this->log('ERROR', "No release found for tag: {$tag} (HTTP {$result['code']})");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -390,7 +468,7 @@ class ReleasePackageCli extends CliFramework
|
|||||||
echo "Release ID: {$releaseId} (tag: {$tag})\n";
|
echo "Release ID: {$releaseId} (tag: {$tag})\n";
|
||||||
|
|
||||||
// ── Delete existing assets with same names ───────────────────────────
|
// ── Delete existing assets with same names ───────────────────────────
|
||||||
$assetsResult = $this->giteaApiRequest("{$apiBase}/releases/{$releaseId}/assets", $token);
|
$assetsResult = $this->gitApiRequest("{$apiBase}/releases/{$releaseId}/assets", $token);
|
||||||
$existingAssets = $assetsResult['data'] ?? [];
|
$existingAssets = $assetsResult['data'] ?? [];
|
||||||
|
|
||||||
$uploadNames = [
|
$uploadNames = [
|
||||||
@@ -407,7 +485,7 @@ class ReleasePackageCli extends CliFramework
|
|||||||
$assetName = $asset['name'] ?? '';
|
$assetName = $asset['name'] ?? '';
|
||||||
$assetId = $asset['id'] ?? 0;
|
$assetId = $asset['id'] ?? 0;
|
||||||
if (in_array($assetName, $uploadNames, true) && $assetId > 0) {
|
if (in_array($assetName, $uploadNames, true) && $assetId > 0) {
|
||||||
$this->giteaApiRequest("{$apiBase}/releases/{$releaseId}/assets/{$assetId}", $token, 'DELETE');
|
$this->gitApiRequest("{$apiBase}/releases/{$releaseId}/assets/{$assetId}", $token, 'DELETE');
|
||||||
echo "Deleted existing asset: {$assetName}\n";
|
echo "Deleted existing asset: {$assetName}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -428,7 +506,7 @@ class ReleasePackageCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
$uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($name);
|
$uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($name);
|
||||||
$httpCode = $this->giteaUploadAsset($uploadUrl, $token, $localPath);
|
$httpCode = $this->gitUploadAsset($uploadUrl, $token, $localPath);
|
||||||
$status = ($httpCode >= 200 && $httpCode < 300) ? 'OK' : "FAILED ({$httpCode})";
|
$status = ($httpCode >= 200 && $httpCode < 300) ? 'OK' : "FAILED ({$httpCode})";
|
||||||
echo "Upload: {$name} — {$status}\n";
|
echo "Upload: {$name} — {$status}\n";
|
||||||
|
|
||||||
@@ -449,11 +527,11 @@ class ReleasePackageCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a Gitea API request.
|
* Perform a Git API request.
|
||||||
*
|
*
|
||||||
* @return array{data: array<string, mixed>|null, code: int}
|
* @return array{data: array<string, mixed>|null, code: int}
|
||||||
*/
|
*/
|
||||||
private function giteaApiRequest(string $url, string $token, string $method = 'GET', ?string $body = null): array
|
private function gitApiRequest(string $url, string $token, string $method = 'GET', ?string $body = null): array
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
if ($ch === false) {
|
if ($ch === false) {
|
||||||
@@ -486,7 +564,7 @@ class ReleasePackageCli extends CliFramework
|
|||||||
/**
|
/**
|
||||||
* Upload a file as a release asset.
|
* Upload a file as a release asset.
|
||||||
*/
|
*/
|
||||||
private function giteaUploadAsset(string $url, string $token, string $filePath): int
|
private function gitUploadAsset(string $url, string $token, string $filePath): int
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
if ($ch === false) {
|
if ($ch === false) {
|
||||||
|
|||||||
+17
-17
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_promote.php
|
* PATH: /cli/release_promote.php
|
||||||
* BRIEF: Promote a Gitea release from one channel to another (rename release, tag, assets)
|
* BRIEF: Promote a Git release from one channel to another (rename release, tag, assets)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,11 +23,11 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Promote a Gitea release from one channel to another');
|
$this->setDescription('Promote a Git release from one channel to another');
|
||||||
$this->addArgument('--from', 'Source channel (or "auto")', '');
|
$this->addArgument('--from', 'Source channel (or "auto")', '');
|
||||||
$this->addArgument('--to', 'Target channel (required)', '');
|
$this->addArgument('--to', 'Target channel (required)', '');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL for the repo', '');
|
$this->addArgument('--api-base', 'Git API base URL for the repo', '');
|
||||||
$this->addArgument('--path', 'Repository root for type prefix detection', '.');
|
$this->addArgument('--path', 'Repository root for type prefix detection', '.');
|
||||||
$this->addArgument('--branch', 'Target branch', 'main');
|
$this->addArgument('--branch', 'Target branch', 'main');
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
$path = $this->getArgument('--path');
|
$path = $this->getArgument('--path');
|
||||||
$branch = $this->getArgument('--branch');
|
$branch = $this->getArgument('--branch');
|
||||||
|
|
||||||
$token = $token ?: (getenv('MOKOGITEA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: null));
|
$token = $token ?: (getenv('MOKOGIT_TOKEN') ?: (getenv('GIT_TOKEN') ?: null));
|
||||||
|
|
||||||
if ($to === null || $token === null || $apiBase === null) {
|
if ($to === null || $token === null || $apiBase === null) {
|
||||||
$this->log('ERROR', "Usage: release_promote.php --from <channel|auto> --to <channel> --token TOKEN --api-base URL [--path .]");
|
$this->log('ERROR', "Usage: release_promote.php --from <channel|auto> --to <channel> --token TOKEN --api-base URL [--path .]");
|
||||||
@@ -64,7 +64,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
// ── Resolve --from auto ──────────────────────────────────────────────────────
|
// ── Resolve --from auto ──────────────────────────────────────────────────────
|
||||||
if ($from === 'auto') {
|
if ($from === 'auto') {
|
||||||
foreach ($channelOrder as $candidate) {
|
foreach ($channelOrder as $candidate) {
|
||||||
$data = $this->giteaApi("{$apiBase}/releases/tags/{$candidate}", $token);
|
$data = $this->gitApi("{$apiBase}/releases/tags/{$candidate}", $token);
|
||||||
if ($data && !empty($data['id'])) {
|
if ($data && !empty($data['id'])) {
|
||||||
$from = $candidate;
|
$from = $candidate;
|
||||||
echo "Auto-detected source channel: {$from}\n";
|
echo "Auto-detected source channel: {$from}\n";
|
||||||
@@ -78,7 +78,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Find source release ──────────────────────────────────────────────────────
|
// ── Find source release ──────────────────────────────────────────────────────
|
||||||
$sourceRelease = $this->giteaApi("{$apiBase}/releases/tags/{$from}", $token);
|
$sourceRelease = $this->gitApi("{$apiBase}/releases/tags/{$from}", $token);
|
||||||
if (!$sourceRelease || empty($sourceRelease['id'])) {
|
if (!$sourceRelease || empty($sourceRelease['id'])) {
|
||||||
$this->log('ERROR', "No release found with tag: {$from}");
|
$this->log('ERROR', "No release found with tag: {$from}");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -90,7 +90,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
echo "Source: {$from} (id: {$sourceId}) — {$sourceName}\n";
|
echo "Source: {$from} (id: {$sourceId}) — {$sourceName}\n";
|
||||||
|
|
||||||
// ── Get source assets ────────────────────────────────────────────────────────
|
// ── Get source assets ────────────────────────────────────────────────────────
|
||||||
$assets = $this->giteaApi("{$apiBase}/releases/{$sourceId}/assets", $token) ?: [];
|
$assets = $this->gitApi("{$apiBase}/releases/{$sourceId}/assets", $token) ?: [];
|
||||||
echo "Assets: " . count($assets) . " file(s)\n";
|
echo "Assets: " . count($assets) . " file(s)\n";
|
||||||
|
|
||||||
// ── Download assets to temp ──────────────────────────────────────────────────
|
// ── Download assets to temp ──────────────────────────────────────────────────
|
||||||
@@ -101,7 +101,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
$name = $asset['name'];
|
$name = $asset['name'];
|
||||||
$downloadUrl = $asset['browser_download_url'];
|
$downloadUrl = $asset['browser_download_url'];
|
||||||
echo " Downloading: {$name}\n";
|
echo " Downloading: {$name}\n";
|
||||||
$this->giteaDownload($downloadUrl, $token, "{$tmpDir}/{$name}");
|
$this->gitDownload($downloadUrl, $token, "{$tmpDir}/{$name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Detect type prefix for stable promotion ──────────────────────────────────
|
// ── Detect type prefix for stable promotion ──────────────────────────────────
|
||||||
@@ -188,15 +188,15 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Delete source release + tag ──────────────────────────────────────────────
|
// ── Delete source release + tag ──────────────────────────────────────────────
|
||||||
$this->giteaApi("{$apiBase}/releases/{$sourceId}", $token, 'DELETE');
|
$this->gitApi("{$apiBase}/releases/{$sourceId}", $token, 'DELETE');
|
||||||
$this->giteaApi("{$apiBase}/tags/{$from}", $token, 'DELETE');
|
$this->gitApi("{$apiBase}/tags/{$from}", $token, 'DELETE');
|
||||||
echo "Deleted source: {$from} release + tag\n";
|
echo "Deleted source: {$from} release + tag\n";
|
||||||
|
|
||||||
// ── Delete existing target release + tag (if any) ────────────────────────────
|
// ── Delete existing target release + tag (if any) ────────────────────────────
|
||||||
$existingTarget = $this->giteaApi("{$apiBase}/releases/tags/{$to}", $token);
|
$existingTarget = $this->gitApi("{$apiBase}/releases/tags/{$to}", $token);
|
||||||
if ($existingTarget && !empty($existingTarget['id'])) {
|
if ($existingTarget && !empty($existingTarget['id'])) {
|
||||||
$this->giteaApi("{$apiBase}/releases/{$existingTarget['id']}", $token, 'DELETE');
|
$this->gitApi("{$apiBase}/releases/{$existingTarget['id']}", $token, 'DELETE');
|
||||||
$this->giteaApi("{$apiBase}/tags/{$to}", $token, 'DELETE');
|
$this->gitApi("{$apiBase}/tags/{$to}", $token, 'DELETE');
|
||||||
echo "Deleted existing target: {$to} release + tag\n";
|
echo "Deleted existing target: {$to} release + tag\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
'prerelease' => $isPrerelease,
|
'prerelease' => $isPrerelease,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$newRelease = $this->giteaApi("{$apiBase}/releases", $token, 'POST', $payload);
|
$newRelease = $this->gitApi("{$apiBase}/releases", $token, 'POST', $payload);
|
||||||
if (!$newRelease || empty($newRelease['id'])) {
|
if (!$newRelease || empty($newRelease['id'])) {
|
||||||
$this->log('ERROR', "Failed to create {$to} release");
|
$this->log('ERROR', "Failed to create {$to} release");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -263,7 +263,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
private function gitApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
@@ -288,7 +288,7 @@ class ReleasePromoteCli extends CliFramework
|
|||||||
return json_decode($response, true) ?: null;
|
return json_decode($response, true) ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaDownload(string $url, string $token, string $dest): bool
|
private function gitDownload(string $url, string $token, string $dest): bool
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
$fp = fopen($dest, 'wb');
|
$fp = fopen($dest, 'wb');
|
||||||
|
|||||||
+17
-17
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/release_publish.php
|
* PATH: /cli/release_publish.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -27,10 +27,10 @@ class ReleasePublishCli extends CliFramework
|
|||||||
$this->setDescription('Publish a release and update stability streams');
|
$this->setDescription('Publish a release and update stability streams');
|
||||||
$this->addArgument('--path', 'Repository root (default: .)', '.');
|
$this->addArgument('--path', 'Repository root (default: .)', '.');
|
||||||
$this->addArgument('--stability', 'Target stability: dev|alpha|beta|rc|stable (required)', '');
|
$this->addArgument('--stability', 'Target stability: dev|alpha|beta|rc|stable (required)', '');
|
||||||
$this->addArgument('--token', 'Gitea API token (required)', '');
|
$this->addArgument('--token', 'Git API token (required)', '');
|
||||||
$this->addArgument('--bump', 'Version bump type: patch|minor|none (default: none)', 'none');
|
$this->addArgument('--bump', 'Version bump type: patch|minor|none (default: none)', 'none');
|
||||||
$this->addArgument('--branch', 'Current branch (default: auto-detect)', '');
|
$this->addArgument('--branch', 'Current branch (default: auto-detect)', '');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL', '');
|
$this->addArgument('--git-url', 'Git URL', '');
|
||||||
$this->addArgument('--org', 'Organization', '');
|
$this->addArgument('--org', 'Organization', '');
|
||||||
$this->addArgument('--repo', 'Repository name', '');
|
$this->addArgument('--repo', 'Repository name', '');
|
||||||
$this->addArgument('--repo-url', 'Repository URL for git auth', '');
|
$this->addArgument('--repo-url', 'Repository URL for git auth', '');
|
||||||
@@ -44,9 +44,9 @@ class ReleasePublishCli extends CliFramework
|
|||||||
$token = $this->getArgument('--token');
|
$token = $this->getArgument('--token');
|
||||||
$bumpType = $this->getArgument('--bump');
|
$bumpType = $this->getArgument('--bump');
|
||||||
$branch = $this->getArgument('--branch');
|
$branch = $this->getArgument('--branch');
|
||||||
$giteaUrl = $this->getArgument('--gitea-url') ?: (getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech');
|
$gitUrl = $this->getArgument('--git-url') ?: (getenv('GIT_URL') ?: 'https://git.mokoconsulting.tech');
|
||||||
$org = $this->getArgument('--org') ?: (getenv('GITEA_ORG') ?: '');
|
$org = $this->getArgument('--org') ?: (getenv('GIT_ORG') ?: '');
|
||||||
$repo = $this->getArgument('--repo') ?: (getenv('GITEA_REPO') ?: '');
|
$repo = $this->getArgument('--repo') ?: (getenv('GIT_REPO') ?: '');
|
||||||
$repoUrl = $this->getArgument('--repo-url');
|
$repoUrl = $this->getArgument('--repo-url');
|
||||||
$skipUpdateStream = $this->getArgument('--skip-update-stream');
|
$skipUpdateStream = $this->getArgument('--skip-update-stream');
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class ReleasePublishCli extends CliFramework
|
|||||||
|
|
||||||
$cli = __DIR__;
|
$cli = __DIR__;
|
||||||
$php = '"' . PHP_BINARY . '"';
|
$php = '"' . PHP_BINARY . '"';
|
||||||
$giteaUrl = rtrim($giteaUrl, '/');
|
$gitUrl = rtrim($gitUrl, '/');
|
||||||
|
|
||||||
// Resolve path early for shell commands (Windows needs native paths)
|
// Resolve path early for shell commands (Windows needs native paths)
|
||||||
$resolvedPath = realpath($path) ?: $path;
|
$resolvedPath = realpath($path) ?: $path;
|
||||||
@@ -81,7 +81,7 @@ class ReleasePublishCli extends CliFramework
|
|||||||
|
|
||||||
// Auto-construct repo URL for git auth if not provided
|
// Auto-construct repo URL for git auth if not provided
|
||||||
if (empty($repoUrl) && !empty($token) && !empty($org) && !empty($repo)) {
|
if (empty($repoUrl) && !empty($token) && !empty($org) && !empty($repo)) {
|
||||||
$host = preg_replace('#^https?://#', '', $giteaUrl);
|
$host = preg_replace('#^https?://#', '', $gitUrl);
|
||||||
$repoUrl = "https://x-access-token:{$token}@{$host}/{$org}/{$repo}.git";
|
$repoUrl = "https://x-access-token:{$token}@{$host}/{$org}/{$repo}.git";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class ReleasePublishCli extends CliFramework
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$apiBase = "{$giteaUrl}/api/v1/repos/{$org}/{$repo}";
|
$apiBase = "{$gitUrl}/api/v1/repos/{$org}/{$repo}";
|
||||||
|
|
||||||
// Stability ordering and suffix mapping
|
// Stability ordering and suffix mapping
|
||||||
$allStabilities = ['dev', 'alpha', 'beta', 'rc', 'stable'];
|
$allStabilities = ['dev', 'alpha', 'beta', 'rc', 'stable'];
|
||||||
@@ -189,11 +189,11 @@ class ReleasePublishCli extends CliFramework
|
|||||||
$cdR = $cdPfx . escapeshellarg($root);
|
$cdR = $cdPfx . escapeshellarg($root);
|
||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdR . " && git config --local user.email"
|
$cdR . " && git config --local user.email"
|
||||||
. " \"gitea-actions[bot]@mokoconsulting.tech\""
|
. " \"git-actions[bot]@mokoconsulting.tech\""
|
||||||
);
|
);
|
||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdR . " && git config --local user.name"
|
$cdR . " && git config --local user.name"
|
||||||
. " \"gitea-actions[bot]\""
|
. " \"git-actions[bot]\""
|
||||||
);
|
);
|
||||||
if (!empty($repoUrl)) {
|
if (!empty($repoUrl)) {
|
||||||
@shell_exec(
|
@shell_exec(
|
||||||
@@ -239,8 +239,8 @@ class ReleasePublishCli extends CliFramework
|
|||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdR . " && git commit -m "
|
$cdR . " && git commit -m "
|
||||||
. escapeshellarg($commitMsg)
|
. escapeshellarg($commitMsg)
|
||||||
. " --author=\"gitea-actions[bot]"
|
. " --author=\"git-actions[bot]"
|
||||||
. " <gitea-actions[bot]@mokoconsulting.tech>\""
|
. " <git-actions[bot]@mokoconsulting.tech>\""
|
||||||
);
|
);
|
||||||
$pushResult = @shell_exec(
|
$pushResult = @shell_exec(
|
||||||
$cdR . " && git push origin "
|
$cdR . " && git push origin "
|
||||||
@@ -314,7 +314,7 @@ class ReleasePublishCli extends CliFramework
|
|||||||
passthru("{$php} {$cli}/updates_xml_build.php --path " . escapeshellarg($path)
|
passthru("{$php} {$cli}/updates_xml_build.php --path " . escapeshellarg($path)
|
||||||
. " --version " . escapeshellarg($streamVersion)
|
. " --version " . escapeshellarg($streamVersion)
|
||||||
. " --stability " . escapeshellarg($stream)
|
. " --stability " . escapeshellarg($stream)
|
||||||
. " --gitea-url " . escapeshellarg($giteaUrl)
|
. " --git-url " . escapeshellarg($gitUrl)
|
||||||
. " --org " . escapeshellarg($org)
|
. " --org " . escapeshellarg($org)
|
||||||
. " --repo " . escapeshellarg($repo)
|
. " --repo " . escapeshellarg($repo)
|
||||||
. " {$shaFlag} 2>&1");
|
. " {$shaFlag} 2>&1");
|
||||||
@@ -339,8 +339,8 @@ class ReleasePublishCli extends CliFramework
|
|||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdRt . " && git commit -m "
|
$cdRt . " && git commit -m "
|
||||||
. escapeshellarg($chMsg)
|
. escapeshellarg($chMsg)
|
||||||
. " --author=\"gitea-actions[bot]"
|
. " --author=\"git-actions[bot]"
|
||||||
. " <gitea-actions[bot]@mokoconsulting.tech>\""
|
. " <git-actions[bot]@mokoconsulting.tech>\""
|
||||||
);
|
);
|
||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdRt . " && git push origin "
|
$cdRt . " && git push origin "
|
||||||
@@ -354,7 +354,7 @@ class ReleasePublishCli extends CliFramework
|
|||||||
. " --current " . escapeshellarg($branch) . " --all"
|
. " --current " . escapeshellarg($branch) . " --all"
|
||||||
. " --version " . escapeshellarg($releaseVersion)
|
. " --version " . escapeshellarg($releaseVersion)
|
||||||
. " --token " . escapeshellarg($token)
|
. " --token " . escapeshellarg($token)
|
||||||
. " --gitea-url " . escapeshellarg($giteaUrl)
|
. " --git-url " . escapeshellarg($gitUrl)
|
||||||
. " --org " . escapeshellarg($org)
|
. " --org " . escapeshellarg($org)
|
||||||
. " --repo " . escapeshellarg($repo) . " 2>&1");
|
. " --repo " . escapeshellarg($repo) . " 2>&1");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ReleaseValidateCli extends CliFramework
|
|||||||
}
|
}
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
if ($platform === null) {
|
if ($platform === null) {
|
||||||
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
$manifestXml = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($manifestXml)) {
|
if (file_exists($manifestXml)) {
|
||||||
$mContent = file_get_contents($manifestXml);
|
$mContent = file_get_contents($manifestXml);
|
||||||
if (preg_match('/<platform>([^<]+)<\/platform>/', $mContent, $pm)) {
|
if (preg_match('/<platform>([^<]+)<\/platform>/', $mContent, $pm)) {
|
||||||
|
|||||||
+5
-5
@@ -22,7 +22,7 @@ use MokoCli\{CliFramework, Config, PlatformAdapterFactory};
|
|||||||
* Interactive repo setup wizard.
|
* Interactive repo setup wizard.
|
||||||
*
|
*
|
||||||
* Walks through platform selection, generates config files, workflows,
|
* Walks through platform selection, generates config files, workflows,
|
||||||
* and optionally creates the repo on Gitea via API.
|
* and optionally creates the repo on Git via API.
|
||||||
*
|
*
|
||||||
* @see https://git.mokoconsulting.tech/MokoConsulting/mokocli/issues/145
|
* @see https://git.mokoconsulting.tech/MokoConsulting/mokocli/issues/145
|
||||||
*/
|
*/
|
||||||
@@ -54,7 +54,7 @@ class RepoWizard extends CliFramework
|
|||||||
{
|
{
|
||||||
$this->setDescription('Interactive configuration wizard for new repositories');
|
$this->setDescription('Interactive configuration wizard for new repositories');
|
||||||
$this->addArgument('--path', 'Directory to generate files in', '.');
|
$this->addArgument('--path', 'Directory to generate files in', '.');
|
||||||
$this->addArgument('--create-remote', 'Create repo on Gitea via API', false);
|
$this->addArgument('--create-remote', 'Create repo on Git via API', false);
|
||||||
$this->addArgument('--non-interactive', 'Use defaults (no prompts)', false);
|
$this->addArgument('--non-interactive', 'Use defaults (no prompts)', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,8 +155,8 @@ class RepoWizard extends CliFramework
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .mokogitea/workflows
|
// .mokogit/workflows
|
||||||
$generated[] = $this->writeFile($path, '.mokogitea/workflows/pr-check.yml',
|
$generated[] = $this->writeFile($path, '.mokogit/workflows/pr-check.yml',
|
||||||
"# PR check workflow — synced from mokocli templates\n# Run: moko sync to update\n");
|
"# PR check workflow — synced from mokocli templates\n# Run: moko sync to update\n");
|
||||||
|
|
||||||
// .gitignore
|
// .gitignore
|
||||||
@@ -205,7 +205,7 @@ class RepoWizard extends CliFramework
|
|||||||
$org = $this->answers['org'];
|
$org = $this->answers['org'];
|
||||||
|
|
||||||
if ($this->dryRun) {
|
if ($this->dryRun) {
|
||||||
$this->log('INFO', "[dry-run] Would create {$org}/{$this->answers['name']} on Gitea");
|
$this->log('INFO', "[dry-run] Would create {$org}/{$this->answers['name']} on Git");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-19
@@ -12,8 +12,8 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/scaffold_client.php
|
* PATH: /cli/scaffold_client.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings
|
* BRIEF: Scaffold a new client-waas repo from Template-Client with pre-configured settings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -26,18 +26,18 @@ class ScaffoldClientCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Scaffold a new client-waas repo from Template-Client-WaaS');
|
$this->setDescription('Scaffold a new client-waas repo from Template-Client');
|
||||||
$this->addArgument('--name', 'Client name', '');
|
$this->addArgument('--name', 'Client name', '');
|
||||||
$this->addArgument('--org', 'Gitea organization', '');
|
$this->addArgument('--org', 'Git organization', '');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL', 'https://git.mokoconsulting.tech');
|
$this->addArgument('--git-url', 'Git URL', 'https://git.mokoconsulting.tech');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$name = $this->getArgument('--name');
|
$name = $this->getArgument('--name');
|
||||||
$org = $this->getArgument('--org');
|
$org = $this->getArgument('--org');
|
||||||
$giteaUrl = rtrim($this->getArgument('--gitea-url'), '/');
|
$gitUrl = rtrim($this->getArgument('--git-url'), '/');
|
||||||
$token = $this->getArgument('--token');
|
$token = $this->getArgument('--token');
|
||||||
if ($name === '' || $org === '' || $token === '') {
|
if ($name === '' || $org === '' || $token === '') {
|
||||||
$this->log('ERROR', '--name, --org, and --token are required.');
|
$this->log('ERROR', '--name, --org, and --token are required.');
|
||||||
@@ -45,11 +45,11 @@ class ScaffoldClientCli extends CliFramework
|
|||||||
}
|
}
|
||||||
$repoName = 'client-waas-' . $name;
|
$repoName = 'client-waas-' . $name;
|
||||||
$this->log('INFO', "Scaffolding client repo: {$org}/{$repoName}");
|
$this->log('INFO', "Scaffolding client repo: {$org}/{$repoName}");
|
||||||
$this->log('INFO', "Gitea URL: {$giteaUrl}");
|
$this->log('INFO', "Git URL: {$gitUrl}");
|
||||||
if ($this->dryRun) {
|
if ($this->dryRun) {
|
||||||
$this->log('INFO', '[DRY RUN] Would create repo from template MokoConsulting/Template-Client-WaaS');
|
$this->log('INFO', '[DRY RUN] Would create repo from template MokoConsulting/Template-Client');
|
||||||
$this->log('INFO', "[DRY RUN] Repo: {$org}/{$repoName}");
|
$this->log('INFO', "[DRY RUN] Repo: {$org}/{$repoName}");
|
||||||
$this->printPostSetupInstructions($repoName, $giteaUrl, $org);
|
$this->printPostSetupInstructions($repoName, $gitUrl, $org);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
$this->log('INFO', 'Step 1: Creating repo from template...');
|
$this->log('INFO', 'Step 1: Creating repo from template...');
|
||||||
@@ -64,8 +64,8 @@ class ScaffoldClientCli extends CliFramework
|
|||||||
]);
|
]);
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
'POST',
|
'POST',
|
||||||
"/api/v1/repos/MokoConsulting/Template-Client-WaaS/generate",
|
"/api/v1/repos/MokoConsulting/Template-Client/generate",
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
$createPayload
|
$createPayload
|
||||||
);
|
);
|
||||||
@@ -75,12 +75,12 @@ class ScaffoldClientCli extends CliFramework
|
|||||||
}
|
}
|
||||||
$this->log('INFO', "Repo created: {$org}/{$repoName}");
|
$this->log('INFO', "Repo created: {$org}/{$repoName}");
|
||||||
$this->log('INFO', 'Step 2: Updating repo description...');
|
$this->log('INFO', 'Step 2: Updating repo description...');
|
||||||
$this->apiRequest('PATCH', "/api/v1/repos/{$org}/{$repoName}", $giteaUrl, $token, json_encode(['description' => "{$name} WaaS site"]));
|
$this->apiRequest('PATCH', "/api/v1/repos/{$org}/{$repoName}", $gitUrl, $token, json_encode(['description' => "{$name} WaaS site"]));
|
||||||
$this->log('INFO', 'Step 3: Creating dev branch from main...');
|
$this->log('INFO', 'Step 3: Creating dev branch from main...');
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
'POST',
|
'POST',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/branches",
|
"/api/v1/repos/{$org}/{$repoName}/branches",
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
json_encode([
|
json_encode([
|
||||||
'new_branch_name' => 'dev',
|
'new_branch_name' => 'dev',
|
||||||
@@ -92,15 +92,15 @@ class ScaffoldClientCli extends CliFramework
|
|||||||
} else {
|
} else {
|
||||||
$this->log('WARN', "Could not create dev branch (HTTP {$response['code']}).");
|
$this->log('WARN', "Could not create dev branch (HTTP {$response['code']}).");
|
||||||
}
|
}
|
||||||
$this->printPostSetupInstructions($repoName, $giteaUrl, $org);
|
$this->printPostSetupInstructions($repoName, $gitUrl, $org);
|
||||||
$this->log('INFO', 'Scaffold complete.');
|
$this->log('INFO', 'Scaffold complete.');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function printPostSetupInstructions(string $repoName, string $giteaUrl, string $org): void
|
private function printPostSetupInstructions(string $repoName, string $gitUrl, string $org): void
|
||||||
{
|
{
|
||||||
fwrite(STDERR, "\n=== POST-SETUP INSTRUCTIONS ===\n\n"
|
fwrite(STDERR, "\n=== POST-SETUP INSTRUCTIONS ===\n\n"
|
||||||
. "Navigate to: {$giteaUrl}/{$org}/{$repoName}/settings\n\n"
|
. "Navigate to: {$gitUrl}/{$org}/{$repoName}/settings\n\n"
|
||||||
. "Set REPO VARIABLES:\n"
|
. "Set REPO VARIABLES:\n"
|
||||||
. " DEV_SYNC_HOST, DEV_SYNC_PORT, DEV_SYNC_USER, DEV_SYNC_PATH\n"
|
. " DEV_SYNC_HOST, DEV_SYNC_PORT, DEV_SYNC_USER, DEV_SYNC_PATH\n"
|
||||||
. " LIVE_SSH_HOST, LIVE_SSH_PORT, LIVE_SSH_USER, LIVE_SYNC_PATH\n\n"
|
. " LIVE_SSH_HOST, LIVE_SSH_PORT, LIVE_SSH_USER, LIVE_SYNC_PATH\n\n"
|
||||||
@@ -109,10 +109,10 @@ class ScaffoldClientCli extends CliFramework
|
|||||||
. "================================\n");
|
. "================================\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function apiRequest(string $method, string $endpoint, string $giteaUrl, string $token, ?string $body = null): array
|
private function apiRequest(string $method, string $endpoint, string $gitUrl, string $token, ?string $body = null): array
|
||||||
{
|
{
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $giteaUrl . $endpoint);
|
curl_setopt($ch, CURLOPT_URL, $gitUrl . $endpoint);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json', "Authorization: token {$token}"]);
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json', "Authorization: token {$token}"]);
|
||||||
|
|||||||
@@ -0,0 +1,294 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: mokocli.CLI
|
||||||
|
* INGROUP: mokocli
|
||||||
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
|
* PATH: /cli/theme_vars_check.php
|
||||||
|
* BRIEF: Validate a client MokoOnyx theme package — required CSS variables
|
||||||
|
* (derived dynamically from the MokoOnyx standard theme) are defined in
|
||||||
|
* the client's light/dark custom CSS, required files exist, the manifest
|
||||||
|
* is sane, and (optionally) the repo's Git metadata is set.
|
||||||
|
*
|
||||||
|
* Standalone (no CliFramework dependency) so it runs even when the shared
|
||||||
|
* framework autoloader is unavailable.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php theme_vars_check.php --path . --reference /tmp/mokoonyx [--github-output]
|
||||||
|
* [--api-base <url> --repo <owner/repo> --token <token>]
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$opts = getopt('', ['path:', 'reference:', 'github-output', 'api-base:', 'repo:', 'token:']);
|
||||||
|
$path = isset($opts['path']) && is_string($opts['path']) && $opts['path'] !== '' ? $opts['path'] : '.';
|
||||||
|
$ref = isset($opts['reference']) && is_string($opts['reference']) ? $opts['reference'] : '';
|
||||||
|
$gh = array_key_exists('github-output', $opts);
|
||||||
|
|
||||||
|
$root = realpath($path);
|
||||||
|
if ($root === false) { $root = $path; }
|
||||||
|
|
||||||
|
$src = is_dir("$root/src") ? "$root/src" : (is_dir("$root/source") ? "$root/source" : null);
|
||||||
|
if ($src === null) {
|
||||||
|
fwrite(STDERR, "ERROR: no src/ or source/ directory under $root\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors = 0;
|
||||||
|
$summary = [];
|
||||||
|
$fail = function (string $msg) use (&$errors, &$summary): void {
|
||||||
|
echo " [FAIL] $msg\n";
|
||||||
|
$summary[] = "FAIL: $msg";
|
||||||
|
$errors++;
|
||||||
|
};
|
||||||
|
$ok = function (string $msg): void { echo " [ok] $msg\n"; };
|
||||||
|
$note = function (string $msg): void { echo " [note] $msg\n"; };
|
||||||
|
|
||||||
|
/** Extract the set of CSS custom-property names DEFINED in a CSS string. */
|
||||||
|
$definedVars = static function (string $css): array {
|
||||||
|
// Matches "--name:" at a declaration position (not var(--name) uses).
|
||||||
|
preg_match_all('/(?:^|[\s;{])(--[a-z0-9_-]+)\s*:/i', $css, $m);
|
||||||
|
return array_values(array_unique(array_map('strtolower', $m[1] ?? [])));
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Find the MokoOnyx standard theme file for a mode inside a reference checkout. */
|
||||||
|
$findStandard = static function (string $ref, string $mode): ?string {
|
||||||
|
if ($ref === '') { return null; }
|
||||||
|
foreach ([
|
||||||
|
"$ref/source/media/css/theme/$mode.standard.css",
|
||||||
|
"$ref/media/templates/site/mokoonyx/css/theme/$mode.standard.css",
|
||||||
|
"$ref/$mode.standard.css",
|
||||||
|
] as $cand) {
|
||||||
|
if (is_file($cand)) { return $cand; }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
$cssDir = "$src/media/templates/site/mokoonyx/css";
|
||||||
|
$themeDir = "$cssDir/theme";
|
||||||
|
$manifest = "$src/templateDetails.xml";
|
||||||
|
$customs = ['light' => "$themeDir/light.custom.css", 'dark' => "$themeDir/dark.custom.css"];
|
||||||
|
|
||||||
|
echo "MokoOnyx theme validation: $src\n\n";
|
||||||
|
|
||||||
|
// 1) Required files -------------------------------------------------------
|
||||||
|
echo "=== Required files ===\n";
|
||||||
|
$requiredFiles = [
|
||||||
|
'templateDetails.xml' => $manifest,
|
||||||
|
'theme/light.custom.css' => $customs['light'],
|
||||||
|
'theme/dark.custom.css' => $customs['dark'],
|
||||||
|
'user.css' => "$cssDir/user.css",
|
||||||
|
];
|
||||||
|
foreach ($requiredFiles as $label => $file) {
|
||||||
|
is_file($file) ? $ok("$label present") : $fail("missing required file: $label");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1b) Forbidden committed files ------------------------------------------
|
||||||
|
echo "\n=== Forbidden files ===\n";
|
||||||
|
$scan = static function (string $dir, array $skip) use (&$scan): array {
|
||||||
|
$out = [];
|
||||||
|
foreach (scandir($dir) ?: [] as $e) {
|
||||||
|
if ($e === '.' || $e === '..') { continue; }
|
||||||
|
$p = "$dir/$e";
|
||||||
|
if (is_dir($p)) {
|
||||||
|
if (in_array($e, $skip, true)) { continue; }
|
||||||
|
$out = array_merge($out, $scan($p, $skip));
|
||||||
|
} else {
|
||||||
|
$out[] = $p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
};
|
||||||
|
$forbidden = [];
|
||||||
|
if (is_dir("$root/.claude")) { $forbidden[] = '.claude/'; }
|
||||||
|
foreach ($scan($root, ['.git', 'vendor', 'node_modules']) as $f) {
|
||||||
|
$b = basename($f);
|
||||||
|
$rel = ltrim(str_replace('\\', '/', substr($f, strlen($root))), '/');
|
||||||
|
if ($b === '.mcp.json' || $b === 'TODO.md'
|
||||||
|
|| fnmatch('.mcp_*.json', $b) || fnmatch('sftp-config*.json', $b)
|
||||||
|
|| fnmatch('*.min.css', $b) || fnmatch('*.min.js', $b)) {
|
||||||
|
$forbidden[] = $rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$forbidden = array_values(array_unique($forbidden));
|
||||||
|
if ($forbidden) {
|
||||||
|
$shown = array_slice($forbidden, 0, 10);
|
||||||
|
$more = count($forbidden) - count($shown);
|
||||||
|
$fail('forbidden file(s) committed: ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||||
|
} else {
|
||||||
|
$ok('no forbidden files committed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) CSS variables — required set derived from the MokoOnyx standard theme
|
||||||
|
echo "\n=== CSS variables vs MokoOnyx standard theme ===\n";
|
||||||
|
if ($ref === '') {
|
||||||
|
$note('no --reference given; skipping variable parity check');
|
||||||
|
} else {
|
||||||
|
foreach ($customs as $mode => $customFile) {
|
||||||
|
$std = $findStandard($ref, $mode);
|
||||||
|
if ($std === null) {
|
||||||
|
$fail("$mode: standard theme not found under reference '$ref'");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!is_file($customFile)) {
|
||||||
|
continue; // already reported missing
|
||||||
|
}
|
||||||
|
$required = $definedVars((string) file_get_contents($std));
|
||||||
|
$defined = $definedVars((string) file_get_contents($customFile));
|
||||||
|
$missing = array_values(array_diff($required, $defined));
|
||||||
|
if ($missing) {
|
||||||
|
$shown = array_slice($missing, 0, 15);
|
||||||
|
$more = count($missing) - count($shown);
|
||||||
|
$fail("$mode mode missing " . count($missing) . '/' . count($required)
|
||||||
|
. ' standard variable(s): ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||||
|
} else {
|
||||||
|
$ok("$mode mode defines all " . count($required) . ' standard variables');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Manifest sanity ------------------------------------------------------
|
||||||
|
echo "\n=== Manifest (templateDetails.xml) ===\n";
|
||||||
|
if (is_file($manifest)) {
|
||||||
|
$xml = @simplexml_load_file($manifest);
|
||||||
|
if ($xml === false) {
|
||||||
|
$fail('templateDetails.xml is not well-formed XML');
|
||||||
|
} else {
|
||||||
|
$version = isset($xml->version) ? trim((string) $xml->version) : '';
|
||||||
|
$version !== '' ? $ok("version $version") : $fail('<version> is missing or empty');
|
||||||
|
|
||||||
|
$server = isset($xml->updateservers->server) ? trim((string) $xml->updateservers->server) : '';
|
||||||
|
if ($server === '') {
|
||||||
|
$fail('<updateservers><server> is missing');
|
||||||
|
} elseif (strpos($server, '/raw/branch/') !== false) {
|
||||||
|
$fail('update server uses a legacy raw/branch URL; use the dynamic MokoGIT feed');
|
||||||
|
} else {
|
||||||
|
$ok("update server: $server");
|
||||||
|
}
|
||||||
|
|
||||||
|
isset($xml->dlid) ? $ok('<dlid> license-key field present')
|
||||||
|
: $fail('<dlid prefix="dlid=" suffix=""/> is missing');
|
||||||
|
|
||||||
|
// Element naming convention (MokoOnyx client theme file package)
|
||||||
|
$type = trim((string) ($xml['type'] ?? ''));
|
||||||
|
$element = isset($xml->element) ? trim((string) $xml->element) : '';
|
||||||
|
if ($type === 'file' && $element !== '') {
|
||||||
|
strpos($element, 'file_mokoonyx_') === 0
|
||||||
|
? $ok("element naming ok ($element)")
|
||||||
|
: $fail("element '$element' should start with 'file_mokoonyx_' for a MokoOnyx client theme package");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required manifest fields
|
||||||
|
foreach (['name', 'element', 'author', 'creationDate'] as $field) {
|
||||||
|
(isset($xml->$field) && trim((string) $xml->$field) !== '')
|
||||||
|
? $ok("<$field> present")
|
||||||
|
: $fail("<$field> is missing or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// scriptfile (if declared) must exist
|
||||||
|
if (isset($xml->scriptfile)) {
|
||||||
|
$sf = trim((string) $xml->scriptfile);
|
||||||
|
is_file("$src/$sf") ? $ok("scriptfile '$sf' present")
|
||||||
|
: $fail("<scriptfile> references '$sf' which is not in src/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fileset integrity — every referenced file/folder must exist in src/
|
||||||
|
if (isset($xml->fileset)) {
|
||||||
|
$missingFs = [];
|
||||||
|
foreach ($xml->fileset->files as $files) {
|
||||||
|
$folder = trim((string) ($files['folder'] ?? ''));
|
||||||
|
$baseDir = $folder !== '' ? "$src/$folder" : $src;
|
||||||
|
foreach ($files->filename as $fn) {
|
||||||
|
$rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fn);
|
||||||
|
if (!is_file("$baseDir/" . trim((string) $fn))) { $missingFs[] = $rel; }
|
||||||
|
}
|
||||||
|
foreach ($files->folder as $fd) {
|
||||||
|
$rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fd) . '/';
|
||||||
|
if (!is_dir("$baseDir/" . trim((string) $fd))) { $missingFs[] = $rel; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($missingFs) {
|
||||||
|
$shown = array_slice($missingFs, 0, 10);
|
||||||
|
$more = count($missingFs) - count($shown);
|
||||||
|
$fail('fileset references ' . count($missingFs) . ' missing path(s): '
|
||||||
|
. implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||||
|
} else {
|
||||||
|
$ok('all fileset paths exist in src/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Repository metadata via Git API (optional) -------------------------
|
||||||
|
$apiBase = isset($opts['api-base']) && is_string($opts['api-base']) ? rtrim($opts['api-base'], '/') : '';
|
||||||
|
$repoSlug = isset($opts['repo']) && is_string($opts['repo']) ? trim($opts['repo']) : '';
|
||||||
|
$token = isset($opts['token']) && is_string($opts['token']) ? trim($opts['token']) : '';
|
||||||
|
if ($apiBase !== '' && $repoSlug !== '' && $token !== '') {
|
||||||
|
echo "\n=== Repository metadata (Git API) ===\n";
|
||||||
|
$url = "$apiBase/repos/$repoSlug";
|
||||||
|
$ctx = stream_context_create(['http' => [
|
||||||
|
'method' => 'GET',
|
||||||
|
'header' => "Authorization: token $token\r\nAccept: application/json\r\n",
|
||||||
|
'ignore_errors' => true,
|
||||||
|
'timeout' => 15,
|
||||||
|
]]);
|
||||||
|
$resp = @file_get_contents($url, false, $ctx);
|
||||||
|
$data = $resp !== false ? json_decode($resp, true) : null;
|
||||||
|
if (!is_array($data) || !isset($data['name'])) {
|
||||||
|
$fail("could not read repo metadata from Git API ($url)");
|
||||||
|
} else {
|
||||||
|
trim((string) ($data['description'] ?? '')) !== '' ? $ok('description set')
|
||||||
|
: $fail('repo description is empty');
|
||||||
|
trim((string) ($data['website'] ?? '')) !== '' ? $ok('website set: ' . $data['website'])
|
||||||
|
: $fail('repo website is empty');
|
||||||
|
$topics = $data['topics'] ?? [];
|
||||||
|
(is_array($topics) && count($topics) > 0) ? $ok(count($topics) . ' topic(s) set')
|
||||||
|
: $fail('repo has no topics');
|
||||||
|
|
||||||
|
// Required topics
|
||||||
|
$requiredTopics = ['joomla', 'client-waas', 'mokoonyx'];
|
||||||
|
$haveTopics = array_map('strtolower', is_array($topics) ? $topics : []);
|
||||||
|
$missingTopics = array_values(array_diff($requiredTopics, $haveTopics));
|
||||||
|
$missingTopics
|
||||||
|
? $fail('missing required topic(s): ' . implode(', ', $missingTopics))
|
||||||
|
: $ok('required topics present (' . implode(', ', $requiredTopics) . ')');
|
||||||
|
|
||||||
|
((string) ($data['default_branch'] ?? '')) === 'main' ? $ok('default branch is main')
|
||||||
|
: $fail("default branch is '" . ($data['default_branch'] ?? '') . "' (expected main)");
|
||||||
|
|
||||||
|
// Branch protection on main
|
||||||
|
$bresp = @file_get_contents("$apiBase/repos/$repoSlug/branches/main", false, $ctx);
|
||||||
|
$bdata = $bresp !== false ? json_decode($bresp, true) : null;
|
||||||
|
if (is_array($bdata) && array_key_exists('protected', $bdata)) {
|
||||||
|
$bdata['protected'] ? $ok('main branch is protected')
|
||||||
|
: $fail('main branch is not protected');
|
||||||
|
} else {
|
||||||
|
$note('could not read branch protection state for main');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "\n=== Repository metadata (Git API) ===\n";
|
||||||
|
$note('API args not provided; skipping repo metadata check');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output / exit -----------------------------------------------------------
|
||||||
|
echo "\n";
|
||||||
|
if ($gh && ($f = getenv('GITHUB_STEP_SUMMARY'))) {
|
||||||
|
$md = "## MokoOnyx Theme Validation\n\n";
|
||||||
|
$md .= $errors === 0
|
||||||
|
? "All checks passed.\n"
|
||||||
|
: implode("\n", array_map(static fn ($l) => "- $l", $summary)) . "\n";
|
||||||
|
@file_put_contents($f, $md, FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors > 0) {
|
||||||
|
echo "FAILED: $errors issue(s) found.\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo "All MokoOnyx theme validation checks passed.\n";
|
||||||
|
exit(0);
|
||||||
+10
-10
@@ -28,7 +28,7 @@ class UpdatesXmlBuildCli extends CliFramework
|
|||||||
$this->addArgument('--version', 'Version string (required)', '');
|
$this->addArgument('--version', 'Version string (required)', '');
|
||||||
$this->addArgument('--stability', 'One of: stable, rc, beta, alpha, development (default: stable)', 'stable');
|
$this->addArgument('--stability', 'One of: stable, rc, beta, alpha, development (default: stable)', 'stable');
|
||||||
$this->addArgument('--sha', 'SHA-256 hash of the ZIP package', '');
|
$this->addArgument('--sha', 'SHA-256 hash of the ZIP package', '');
|
||||||
$this->addArgument('--gitea-url', 'Gitea instance URL', '');
|
$this->addArgument('--git-url', 'Git instance URL', '');
|
||||||
$this->addArgument('--org', 'Organization', '');
|
$this->addArgument('--org', 'Organization', '');
|
||||||
$this->addArgument('--repo', 'Repository name', '');
|
$this->addArgument('--repo', 'Repository name', '');
|
||||||
$this->addArgument('--output', 'Output file path (default: updates.xml in --path)', '');
|
$this->addArgument('--output', 'Output file path (default: updates.xml in --path)', '');
|
||||||
@@ -41,9 +41,9 @@ class UpdatesXmlBuildCli extends CliFramework
|
|||||||
$version = $this->getArgument('--version');
|
$version = $this->getArgument('--version');
|
||||||
$stability = $this->getArgument('--stability');
|
$stability = $this->getArgument('--stability');
|
||||||
$sha = $this->getArgument('--sha') ?: null;
|
$sha = $this->getArgument('--sha') ?: null;
|
||||||
$giteaUrl = $this->getArgument('--gitea-url') ?: (getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech');
|
$gitUrl = $this->getArgument('--git-url') ?: (getenv('GIT_URL') ?: 'https://git.mokoconsulting.tech');
|
||||||
$org = $this->getArgument('--org') ?: (getenv('GITEA_ORG') ?: '');
|
$org = $this->getArgument('--org') ?: (getenv('GIT_ORG') ?: '');
|
||||||
$repo = $this->getArgument('--repo') ?: (getenv('GITEA_REPO') ?: '');
|
$repo = $this->getArgument('--repo') ?: (getenv('GIT_REPO') ?: '');
|
||||||
$outputFile = $this->getArgument('--output') ?: null;
|
$outputFile = $this->getArgument('--output') ?: null;
|
||||||
$githubOutput = $this->getArgument('--github-output');
|
$githubOutput = $this->getArgument('--github-output');
|
||||||
|
|
||||||
@@ -57,12 +57,12 @@ class UpdatesXmlBuildCli extends CliFramework
|
|||||||
|
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
// -- Read platform from .mokogitea/manifest.xml --------------------------------
|
// -- Read platform from .mokogit/manifest.xml --------------------------------
|
||||||
$detectedPlatform = 'joomla';
|
$detectedPlatform = 'joomla';
|
||||||
$detectedName = $repo;
|
$detectedName = $repo;
|
||||||
$detectedPackageType = '';
|
$detectedPackageType = '';
|
||||||
$detectedDisplayName = '';
|
$detectedDisplayName = '';
|
||||||
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
$mokoManifest = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($mokoManifest)) {
|
if (file_exists($mokoManifest)) {
|
||||||
$mokoXml = @simplexml_load_file($mokoManifest);
|
$mokoXml = @simplexml_load_file($mokoManifest);
|
||||||
if ($mokoXml !== false) {
|
if ($mokoXml !== false) {
|
||||||
@@ -312,12 +312,12 @@ class UpdatesXmlBuildCli extends CliFramework
|
|||||||
|
|
||||||
// -- Write ONLY the single channel being released --------------------------------
|
// -- Write ONLY the single channel being released --------------------------------
|
||||||
$entries = [];
|
$entries = [];
|
||||||
$giteaTag = $releaseTagMap[$stability] ?? $stability;
|
$gitTag = $releaseTagMap[$stability] ?? $stability;
|
||||||
$channelVersion = $version . ($stabilitySuffixMap[$stability] ?? '');
|
$channelVersion = $version . ($stabilitySuffixMap[$stability] ?? '');
|
||||||
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$giteaTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
|
$channelDownloadUrl = "{$gitUrl}/{$org}/{$repo}/releases/download/{$gitTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
|
||||||
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$giteaTag}";
|
$channelInfoUrl = "{$gitUrl}/{$org}/{$repo}/releases/tag/{$gitTag}";
|
||||||
$joomlaTag = $stabilityTagMap[$stability] ?? $stability;
|
$joomlaTag = $stabilityTagMap[$stability] ?? $stability;
|
||||||
$changelogUrl = "{$giteaUrl}/{$org}/{$repo}/raw/branch/main/CHANGELOG.md";
|
$changelogUrl = "{$gitUrl}/{$org}/{$repo}/raw/branch/main/CHANGELOG.md";
|
||||||
|
|
||||||
$entries[] = $this->buildEntry(
|
$entries[] = $this->buildEntry(
|
||||||
$joomlaTag,
|
$joomlaTag,
|
||||||
|
|||||||
+17
-17
@@ -10,8 +10,8 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/updates_xml_sync.php
|
* PATH: /cli/updates_xml_sync.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Sync updates.xml to target branches via Gitea API
|
* BRIEF: Sync updates.xml to target branches via Git API
|
||||||
* NOTE: Called by pre-release and auto-release workflows after updates.xml
|
* NOTE: Called by pre-release and auto-release workflows after updates.xml
|
||||||
* is modified on the current branch. Pushes the file to other branches
|
* is modified on the current branch. Pushes the file to other branches
|
||||||
* without requiring a git checkout (avoids merge conflicts).
|
* without requiring a git checkout (avoids merge conflicts).
|
||||||
@@ -27,14 +27,14 @@ class UpdatesXmlSyncCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Sync updates.xml to target branches via Gitea API');
|
$this->setDescription('Sync updates.xml to target branches via Git API');
|
||||||
$this->addArgument('--path', 'Repository root containing updates.xml', '.');
|
$this->addArgument('--path', 'Repository root containing updates.xml', '.');
|
||||||
$this->addArgument('--branches', 'Comma-separated target branches to sync to', 'main,dev');
|
$this->addArgument('--branches', 'Comma-separated target branches to sync to', 'main,dev');
|
||||||
$this->addArgument('--all', 'Auto-discover all branches via Gitea API', false);
|
$this->addArgument('--all', 'Auto-discover all branches via Git API', false);
|
||||||
$this->addArgument('--current', 'Current branch to skip (required)', '');
|
$this->addArgument('--current', 'Current branch to skip (required)', '');
|
||||||
$this->addArgument('--version', 'Version string for commit message', '');
|
$this->addArgument('--version', 'Version string for commit message', '');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--gitea-url', 'Gitea instance URL', '');
|
$this->addArgument('--git-url', 'Git instance URL', '');
|
||||||
$this->addArgument('--org', 'Organization', '');
|
$this->addArgument('--org', 'Organization', '');
|
||||||
$this->addArgument('--repo', 'Repository name', '');
|
$this->addArgument('--repo', 'Repository name', '');
|
||||||
}
|
}
|
||||||
@@ -47,22 +47,22 @@ class UpdatesXmlSyncCli extends CliFramework
|
|||||||
$current = $this->getArgument('--current');
|
$current = $this->getArgument('--current');
|
||||||
$version = $this->getArgument('--version');
|
$version = $this->getArgument('--version');
|
||||||
$token = $this->getArgument('--token');
|
$token = $this->getArgument('--token');
|
||||||
$giteaUrl = $this->getArgument('--gitea-url');
|
$gitUrl = $this->getArgument('--git-url');
|
||||||
$org = $this->getArgument('--org');
|
$org = $this->getArgument('--org');
|
||||||
$repo = $this->getArgument('--repo');
|
$repo = $this->getArgument('--repo');
|
||||||
|
|
||||||
// Fall back to environment variables
|
// Fall back to environment variables
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$token = getenv('MOKOGITEA_TOKEN') ?: '';
|
$token = getenv('MOKOGIT_TOKEN') ?: '';
|
||||||
}
|
}
|
||||||
if ($giteaUrl === '') {
|
if ($gitUrl === '') {
|
||||||
$giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
$gitUrl = getenv('GIT_URL') ?: 'https://git.mokoconsulting.tech';
|
||||||
}
|
}
|
||||||
if ($org === '') {
|
if ($org === '') {
|
||||||
$org = getenv('GITEA_ORG') ?: '';
|
$org = getenv('GIT_ORG') ?: '';
|
||||||
}
|
}
|
||||||
if ($repo === '') {
|
if ($repo === '') {
|
||||||
$repo = getenv('GITEA_REPO') ?: '';
|
$repo = getenv('GIT_REPO') ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($current === '') {
|
if ($current === '') {
|
||||||
@@ -71,18 +71,18 @@ class UpdatesXmlSyncCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$this->log('ERROR', '--token or MOKOGITEA_TOKEN env is required');
|
$this->log('ERROR', '--token or MOKOGIT_TOKEN env is required');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($org === '' || $repo === '') {
|
if ($org === '' || $repo === '') {
|
||||||
$this->log('ERROR', '--org and --repo (or GITEA_ORG/GITEA_REPO env) are required');
|
$this->log('ERROR', '--org and --repo (or GIT_ORG/GIT_REPO env) are required');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-discover branches if --all flag is set
|
// Auto-discover branches if --all flag is set
|
||||||
if ($discoverAll) {
|
if ($discoverAll) {
|
||||||
$apiUrl = "{$giteaUrl}/api/v1/repos/{$org}/{$repo}/branches?limit=50";
|
$apiUrl = "{$gitUrl}/api/v1/repos/{$org}/{$repo}/branches?limit=50";
|
||||||
$ch = curl_init($apiUrl);
|
$ch = curl_init($apiUrl);
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
@@ -118,8 +118,8 @@ class UpdatesXmlSyncCli extends CliFramework
|
|||||||
|
|
||||||
$content = file_get_contents($updatesFile);
|
$content = file_get_contents($updatesFile);
|
||||||
$encoded = base64_encode($content);
|
$encoded = base64_encode($content);
|
||||||
$giteaUrl = rtrim($giteaUrl, '/');
|
$gitUrl = rtrim($gitUrl, '/');
|
||||||
$apiBase = "{$giteaUrl}/api/v1/repos/{$org}/{$repo}";
|
$apiBase = "{$gitUrl}/api/v1/repos/{$org}/{$repo}";
|
||||||
$vLabel = $version !== '' ? " {$version}" : '';
|
$vLabel = $version !== '' ? " {$version}" : '';
|
||||||
|
|
||||||
$targets = array_filter(
|
$targets = array_filter(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/version_auto_bump.php
|
* PATH: /cli/version_auto_bump.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class VersionAutoBumpCli extends CliFramework
|
|||||||
|
|
||||||
// Auto-detect watch path from manifest.xml if not provided
|
// Auto-detect watch path from manifest.xml if not provided
|
||||||
if (empty($watchPath)) {
|
if (empty($watchPath)) {
|
||||||
$manifestFile = realpath($path) . '/.mokogitea/manifest.xml';
|
$manifestFile = realpath($path) . '/.mokogit/manifest.xml';
|
||||||
if (file_exists($manifestFile)) {
|
if (file_exists($manifestFile)) {
|
||||||
$xml = @simplexml_load_file($manifestFile);
|
$xml = @simplexml_load_file($manifestFile);
|
||||||
if ($xml && isset($xml->build->{'entry-point'})) {
|
if ($xml && isset($xml->build->{'entry-point'})) {
|
||||||
@@ -176,11 +176,11 @@ class VersionAutoBumpCli extends CliFramework
|
|||||||
$cdRoot = $cd . escapeshellarg($root);
|
$cdRoot = $cd . escapeshellarg($root);
|
||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdRoot . " && git config --local user.email"
|
$cdRoot . " && git config --local user.email"
|
||||||
. " \"gitea-actions[bot]@mokoconsulting.tech\""
|
. " \"git-actions[bot]@mokoconsulting.tech\""
|
||||||
);
|
);
|
||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdRoot . " && git config --local user.name"
|
$cdRoot . " && git config --local user.name"
|
||||||
. " \"gitea-actions[bot]\""
|
. " \"git-actions[bot]\""
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!empty($repoUrl)) {
|
if (!empty($repoUrl)) {
|
||||||
@@ -196,8 +196,8 @@ class VersionAutoBumpCli extends CliFramework
|
|||||||
: "chore(version): set {$stability} suffix {$displayVersion} [skip ci]";
|
: "chore(version): set {$stability} suffix {$displayVersion} [skip ci]";
|
||||||
@shell_exec(
|
@shell_exec(
|
||||||
$cdRoot . " && git commit -m " . escapeshellarg($commitMsg)
|
$cdRoot . " && git commit -m " . escapeshellarg($commitMsg)
|
||||||
. " --author=\"gitea-actions[bot]"
|
. " --author=\"git-actions[bot]"
|
||||||
. " <gitea-actions[bot]@mokoconsulting.tech>\""
|
. " <git-actions[bot]@mokoconsulting.tech>\""
|
||||||
);
|
);
|
||||||
|
|
||||||
$pushResult = @shell_exec(
|
$pushResult = @shell_exec(
|
||||||
|
|||||||
+10
-5
@@ -44,7 +44,7 @@ class VersionBumpCli extends CliFramework
|
|||||||
$mokoVersion = null;
|
$mokoVersion = null;
|
||||||
$existingSuffix = '';
|
$existingSuffix = '';
|
||||||
$versionPrefix = '';
|
$versionPrefix = '';
|
||||||
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
$mokoManifest = "{$root}/.mokogit/manifest.xml";
|
||||||
$mokoContent = '';
|
$mokoContent = '';
|
||||||
if (file_exists($mokoManifest)) {
|
if (file_exists($mokoManifest)) {
|
||||||
$mokoContent = file_get_contents($mokoManifest);
|
$mokoContent = file_get_contents($mokoManifest);
|
||||||
@@ -214,7 +214,8 @@ class VersionBumpCli extends CliFramework
|
|||||||
$newContent = preg_replace(
|
$newContent = preg_replace(
|
||||||
$xmlPattern,
|
$xmlPattern,
|
||||||
'${1}' . $newBase . '</version>',
|
'${1}' . $newBase . '</version>',
|
||||||
$content
|
$content,
|
||||||
|
1
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$xmlPattern = '#<version>\d{2}\.\d{2}\.\d{2}'
|
$xmlPattern = '#<version>\d{2}\.\d{2}\.\d{2}'
|
||||||
@@ -222,9 +223,13 @@ class VersionBumpCli extends CliFramework
|
|||||||
$newContent = preg_replace(
|
$newContent = preg_replace(
|
||||||
$xmlPattern,
|
$xmlPattern,
|
||||||
"<version>{$newFull}</version>",
|
"<version>{$newFull}</version>",
|
||||||
$content
|
$content,
|
||||||
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if ($newContent !== null) {
|
||||||
|
$newContent = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $newContent);
|
||||||
|
}
|
||||||
if ($newContent !== null && $newContent !== $content) {
|
if ($newContent !== null && $newContent !== $content) {
|
||||||
file_put_contents($xmlFile, $newContent);
|
file_put_contents($xmlFile, $newContent);
|
||||||
$updatedFiles[] = substr($xmlFile, strlen($root) + 1);
|
$updatedFiles[] = substr($xmlFile, strlen($root) + 1);
|
||||||
@@ -358,7 +363,7 @@ class VersionBumpCli extends CliFramework
|
|||||||
if (in_array($relPath, $updatedFiles ?? [], true)) {
|
if (in_array($relPath, $updatedFiles ?? [], true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (strpos($relPath, '.mokogitea/manifest.xml') !== false) {
|
if (strpos($relPath, '.mokogit/manifest.xml') !== false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$content = @file_get_contents($filePath);
|
$content = @file_get_contents($filePath);
|
||||||
@@ -384,7 +389,7 @@ class VersionBumpCli extends CliFramework
|
|||||||
/**
|
/**
|
||||||
* Scan git release tags for the highest version across all channels.
|
* Scan git release tags for the highest version across all channels.
|
||||||
*
|
*
|
||||||
* Checks release names like "MokoSuiteClient (VERSION: 09.40.00)" in
|
* Checks release names like "MokoSuiteClient (VERSION: 09.43.00)" in
|
||||||
* git tags (stable, release-candidate, development, etc.) to find the
|
* git tags (stable, release-candidate, development, etc.) to find the
|
||||||
* highest version that has been released on any channel.
|
* highest version that has been released on any channel.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+13
-13
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/version_bump_remote.php
|
* PATH: /cli/version_bump_remote.php
|
||||||
* BRIEF: Bump version in manifest XML and CHANGELOG.md on a remote branch via Gitea API
|
* BRIEF: Bump version in manifest XML and CHANGELOG.md on a remote branch via Git API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,15 +23,15 @@ class VersionBumpRemoteCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Bump version in manifest XML and CHANGELOG.md on a remote branch via Gitea API');
|
$this->setDescription('Bump version in manifest XML and CHANGELOG.md on a remote branch via Git API');
|
||||||
$this->addArgument('--path', 'Repository root', '.');
|
$this->addArgument('--path', 'Repository root', '.');
|
||||||
$this->addArgument('--branch', 'Target branch to bump (required)', null);
|
$this->addArgument('--branch', 'Target branch to bump (required)', null);
|
||||||
$this->addArgument('--bump', 'Bump type: patch | minor | major', 'minor');
|
$this->addArgument('--bump', 'Bump type: patch | minor | major', 'minor');
|
||||||
$this->addArgument('--token', 'Gitea API token (or MOKOGITEA_TOKEN env var)', null);
|
$this->addArgument('--token', 'Git API token (or MOKOGIT_TOKEN env var)', null);
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL for the repo', null);
|
$this->addArgument('--api-base', 'Git API base URL for the repo', null);
|
||||||
$this->addArgument('--no-changelog', 'Skip CHANGELOG.md bump', false);
|
$this->addArgument('--no-changelog', 'Skip CHANGELOG.md bump', false);
|
||||||
$this->addArgument('--repo', 'Repository path (owner/repo)', null);
|
$this->addArgument('--repo', 'Repository path (owner/repo)', null);
|
||||||
$this->addArgument('--gitea-url', 'Gitea instance URL', null);
|
$this->addArgument('--git-url', 'Git instance URL', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
@@ -43,15 +43,15 @@ class VersionBumpRemoteCli extends CliFramework
|
|||||||
$apiBase = $this->getArgument('--api-base');
|
$apiBase = $this->getArgument('--api-base');
|
||||||
$noChangelog = (bool) $this->getArgument('--no-changelog');
|
$noChangelog = (bool) $this->getArgument('--no-changelog');
|
||||||
$repo = $this->getArgument('--repo');
|
$repo = $this->getArgument('--repo');
|
||||||
$giteaUrl = $this->getArgument('--gitea-url');
|
$gitUrl = $this->getArgument('--git-url');
|
||||||
if ($token === null) {
|
if ($token === null) {
|
||||||
$token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
$token = getenv('MOKOGIT_TOKEN') ?: getenv('GIT_TOKEN') ?: null;
|
||||||
}
|
}
|
||||||
if ($giteaUrl === null) {
|
if ($gitUrl === null) {
|
||||||
$giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
$gitUrl = getenv('GIT_URL') ?: 'https://git.mokoconsulting.tech';
|
||||||
}
|
}
|
||||||
if ($apiBase === null && $repo !== null) {
|
if ($apiBase === null && $repo !== null) {
|
||||||
$apiBase = rtrim($giteaUrl, '/') . '/api/v1/repos/' . $repo;
|
$apiBase = rtrim($gitUrl, '/') . '/api/v1/repos/' . $repo;
|
||||||
}
|
}
|
||||||
if ($branch === null || $token === null || $apiBase === null) {
|
if ($branch === null || $token === null || $apiBase === null) {
|
||||||
$this->log('ERROR', "Usage: version_bump_remote.php --branch BRANCH --token TOKEN --api-base URL [--bump minor|patch|major]");
|
$this->log('ERROR', "Usage: version_bump_remote.php --branch BRANCH --token TOKEN --api-base URL [--bump minor|patch|major]");
|
||||||
@@ -148,7 +148,7 @@ class VersionBumpRemoteCli extends CliFramework
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaApi(string $method, string $url, string $token, ?string $body = null): ?array
|
private function gitApi(string $method, string $url, string $token, ?string $body = null): ?array
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
@@ -180,7 +180,7 @@ class VersionBumpRemoteCli extends CliFramework
|
|||||||
callable $transform,
|
callable $transform,
|
||||||
string $commitMessage
|
string $commitMessage
|
||||||
): bool {
|
): bool {
|
||||||
$file = $this->giteaApi('GET', "{$apiBase}/contents/{$filePath}?ref={$branch}", $token);
|
$file = $this->gitApi('GET', "{$apiBase}/contents/{$filePath}?ref={$branch}", $token);
|
||||||
if ($file === null || !isset($file['sha']) || !isset($file['content'])) {
|
if ($file === null || !isset($file['sha']) || !isset($file['content'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ class VersionBumpRemoteCli extends CliFramework
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$payload = json_encode(['content' => base64_encode($newContent), 'sha' => $file['sha'], 'message' => $commitMessage, 'branch' => $branch]);
|
$payload = json_encode(['content' => base64_encode($newContent), 'sha' => $file['sha'], 'message' => $commitMessage, 'branch' => $branch]);
|
||||||
$result = $this->giteaApi('PUT', "{$apiBase}/contents/{$filePath}", $token, $payload);
|
$result = $this->gitApi('PUT', "{$apiBase}/contents/{$filePath}", $token, $payload);
|
||||||
if ($result === null) {
|
if ($result === null) {
|
||||||
$this->log('ERROR', "{$filePath}: failed to update");
|
$this->log('ERROR', "{$filePath}: failed to update");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+11
-8
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/version_check.php
|
* PATH: /cli/version_check.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -38,14 +38,14 @@ class VersionCheckCli extends CliFramework
|
|||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
$errors = 0;
|
$errors = 0;
|
||||||
$versions = [];
|
$versions = [];
|
||||||
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
$mokoManifest = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($mokoManifest)) {
|
if (file_exists($mokoManifest)) {
|
||||||
$xml = @simplexml_load_file($mokoManifest);
|
$xml = @simplexml_load_file($mokoManifest);
|
||||||
if ($xml !== false) {
|
if ($xml !== false) {
|
||||||
$v = (string)($xml->identity->version ?? '');
|
$v = (string)($xml->identity->version ?? '');
|
||||||
$base = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $v);
|
$base = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $v);
|
||||||
if (preg_match('/^\d{2}\.\d{2}\.\d{2}$/', $base)) {
|
if (preg_match('/^\d{2}\.\d{2}\.\d{2}$/', $base)) {
|
||||||
$versions['.mokogitea/manifest.xml'] = $base;
|
$versions['.mokogit/manifest.xml'] = $base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,18 +124,20 @@ class VersionCheckCli extends CliFramework
|
|||||||
file_put_contents($readme, $updated);
|
file_put_contents($readme, $updated);
|
||||||
} echo " Fixed: README.md -> {$highestVersion}\n";
|
} echo " Fixed: README.md -> {$highestVersion}\n";
|
||||||
}
|
}
|
||||||
if (isset($versions['.mokogitea/manifest.xml']) && $versions['.mokogitea/manifest.xml'] !== $highestVersion) {
|
if (isset($versions['.mokogit/manifest.xml']) && $versions['.mokogit/manifest.xml'] !== $highestVersion) {
|
||||||
$content = file_get_contents($mokoManifest);
|
$content = file_get_contents($mokoManifest);
|
||||||
$vPat = '#<version>\d{2}\.\d{2}\.\d{2}'
|
$vPat = '#<version>\d{2}\.\d{2}\.\d{2}'
|
||||||
. '(?:(?:-(?:dev|alpha|beta|rc))+)?</version>#';
|
. '(?:(?:-(?:dev|alpha|beta|rc))+)?</version>#';
|
||||||
$updated = preg_replace(
|
$updated = preg_replace(
|
||||||
$vPat,
|
$vPat,
|
||||||
"<version>{$highestVersion}</version>",
|
"<version>{$highestVersion}</version>",
|
||||||
$content
|
$content,
|
||||||
|
1
|
||||||
);
|
);
|
||||||
if ($updated !== null) {
|
if ($updated !== null) {
|
||||||
|
$updated = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $updated);
|
||||||
file_put_contents($mokoManifest, $updated);
|
file_put_contents($mokoManifest, $updated);
|
||||||
} echo " Fixed: .mokogitea/manifest.xml -> {$highestVersion}\n";
|
} echo " Fixed: .mokogit/manifest.xml -> {$highestVersion}\n";
|
||||||
}
|
}
|
||||||
if (isset($versions['CHANGELOG.md']) && $versions['CHANGELOG.md'] !== $highestVersion) {
|
if (isset($versions['CHANGELOG.md']) && $versions['CHANGELOG.md'] !== $highestVersion) {
|
||||||
$content = file_get_contents($changelog);
|
$content = file_get_contents($changelog);
|
||||||
@@ -177,7 +179,7 @@ class VersionCheckCli extends CliFramework
|
|||||||
} echo " Fixed: pyproject.toml -> {$highestVersion}\n";
|
} echo " Fixed: pyproject.toml -> {$highestVersion}\n";
|
||||||
}
|
}
|
||||||
foreach ($versions as $source => $ver) {
|
foreach ($versions as $source => $ver) {
|
||||||
if (in_array($source, ['README.md', 'CHANGELOG.md', '.mokogitea/manifest.xml', 'package.json', 'pyproject.toml'], true)) {
|
if (in_array($source, ['README.md', 'CHANGELOG.md', '.mokogit/manifest.xml', 'package.json', 'pyproject.toml'], true)) {
|
||||||
continue;
|
continue;
|
||||||
} if ($ver === $highestVersion) {
|
} if ($ver === $highestVersion) {
|
||||||
continue;
|
continue;
|
||||||
@@ -185,8 +187,9 @@ class VersionCheckCli extends CliFramework
|
|||||||
if (!file_exists($file)) {
|
if (!file_exists($file)) {
|
||||||
continue;
|
continue;
|
||||||
} $content = file_get_contents($file);
|
} $content = file_get_contents($file);
|
||||||
$updated = preg_replace('#<version>[^<]*</version>#', "<version>{$highestVersion}</version>", $content);
|
$updated = preg_replace('#<version>[^<]*</version>#', "<version>{$highestVersion}</version>", $content, 1);
|
||||||
if ($updated !== null) {
|
if ($updated !== null) {
|
||||||
|
$updated = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $updated);
|
||||||
file_put_contents($file, $updated);
|
file_put_contents($file, $updated);
|
||||||
} echo " Fixed: {$source} -> {$highestVersion}\n";
|
} echo " Fixed: {$source} -> {$highestVersion}\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ class VersionReadCli extends CliFramework
|
|||||||
$path = $this->getArgument('--path');
|
$path = $this->getArgument('--path');
|
||||||
$root = realpath($path) ?: $path;
|
$root = realpath($path) ?: $path;
|
||||||
|
|
||||||
// -- 1. Read from .mokogitea/manifest.xml (canonical source) --
|
// -- 1. Read from .mokogit/manifest.xml (canonical source) --
|
||||||
$mokoVersion = null;
|
$mokoVersion = null;
|
||||||
$versionPrefix = '';
|
$versionPrefix = '';
|
||||||
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
|
$mokoManifest = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($mokoManifest)) {
|
if (file_exists($mokoManifest)) {
|
||||||
$xml = @simplexml_load_file($mokoManifest);
|
$xml = @simplexml_load_file($mokoManifest);
|
||||||
if ($xml !== false) {
|
if ($xml !== false) {
|
||||||
|
|||||||
+11
-11
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/version_reset_dev.php
|
* PATH: /cli/version_reset_dev.php
|
||||||
* BRIEF: Reset platform version to 'development' on a branch via Gitea API
|
* BRIEF: Reset platform version to 'development' on a branch via Git API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@@ -23,9 +23,9 @@ class VersionResetDevCli extends CliFramework
|
|||||||
{
|
{
|
||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Reset platform version to development on a branch via Gitea API');
|
$this->setDescription('Reset platform version to development on a branch via Git API');
|
||||||
$this->addArgument('--token', 'Gitea API token (also reads MOKOGITEA_TOKEN / GITEA_TOKEN env)', '');
|
$this->addArgument('--token', 'Git API token (also reads MOKOGIT_TOKEN / GIT_TOKEN env)', '');
|
||||||
$this->addArgument('--api-base', 'Gitea API base URL for the repo', '');
|
$this->addArgument('--api-base', 'Git API base URL for the repo', '');
|
||||||
$this->addArgument('--branch', 'Target branch (default: dev)', 'dev');
|
$this->addArgument('--branch', 'Target branch (default: dev)', 'dev');
|
||||||
$this->addArgument('--platform', 'Platform type: dolibarr, crm-module, joomla, waas-component', '');
|
$this->addArgument('--platform', 'Platform type: dolibarr, crm-module, joomla, waas-component', '');
|
||||||
$this->addArgument('--path', 'Repo root for auto-detecting platform from manifest.xml', '');
|
$this->addArgument('--path', 'Repo root for auto-detecting platform from manifest.xml', '');
|
||||||
@@ -41,13 +41,13 @@ class VersionResetDevCli extends CliFramework
|
|||||||
|
|
||||||
// Allow token from environment
|
// Allow token from environment
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$envToken = getenv('MOKOGITEA_TOKEN');
|
$envToken = getenv('MOKOGIT_TOKEN');
|
||||||
if ($envToken !== false && $envToken !== '') {
|
if ($envToken !== false && $envToken !== '') {
|
||||||
$token = $envToken;
|
$token = $envToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
$envToken = getenv('GITEA_TOKEN');
|
$envToken = getenv('GIT_TOKEN');
|
||||||
if ($envToken !== false && $envToken !== '') {
|
if ($envToken !== false && $envToken !== '') {
|
||||||
$token = $envToken;
|
$token = $envToken;
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ class VersionResetDevCli extends CliFramework
|
|||||||
private function detectPlatform(string $repoPath): ?string
|
private function detectPlatform(string $repoPath): ?string
|
||||||
{
|
{
|
||||||
$root = realpath($repoPath) ?: $repoPath;
|
$root = realpath($repoPath) ?: $repoPath;
|
||||||
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
$manifestXml = "{$root}/.mokogit/manifest.xml";
|
||||||
|
|
||||||
if (!file_exists($manifestXml)) {
|
if (!file_exists($manifestXml)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -114,7 +114,7 @@ class VersionResetDevCli extends CliFramework
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function giteaApiCall(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
private function gitApiCall(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
|
||||||
{
|
{
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
if ($ch === false) {
|
if ($ch === false) {
|
||||||
@@ -161,7 +161,7 @@ class VersionResetDevCli extends CliFramework
|
|||||||
{
|
{
|
||||||
// Search the repo tree for mod*.class.php files
|
// Search the repo tree for mod*.class.php files
|
||||||
$treeUrl = "{$apiBase}/git/trees/{$branch}?recursive=true";
|
$treeUrl = "{$apiBase}/git/trees/{$branch}?recursive=true";
|
||||||
$tree = $this->giteaApiCall($treeUrl, $token);
|
$tree = $this->gitApiCall($treeUrl, $token);
|
||||||
|
|
||||||
if ($tree === null || !isset($tree['tree']) || !is_array($tree['tree'])) {
|
if ($tree === null || !isset($tree['tree']) || !is_array($tree['tree'])) {
|
||||||
$this->log('ERROR', "Could not read repository tree for branch '{$branch}'.");
|
$this->log('ERROR', "Could not read repository tree for branch '{$branch}'.");
|
||||||
@@ -191,7 +191,7 @@ class VersionResetDevCli extends CliFramework
|
|||||||
// GET file contents via API
|
// GET file contents via API
|
||||||
$encodedPath = implode('/', array_map('rawurlencode', explode('/', $filePath)));
|
$encodedPath = implode('/', array_map('rawurlencode', explode('/', $filePath)));
|
||||||
$fileUrl = "{$apiBase}/contents/{$encodedPath}?ref={$branch}";
|
$fileUrl = "{$apiBase}/contents/{$encodedPath}?ref={$branch}";
|
||||||
$fileData = $this->giteaApiCall($fileUrl, $token);
|
$fileData = $this->gitApiCall($fileUrl, $token);
|
||||||
|
|
||||||
if ($fileData === null || !isset($fileData['content'])) {
|
if ($fileData === null || !isset($fileData['content'])) {
|
||||||
echo "Skipping {$filePath}: could not fetch contents.\n";
|
echo "Skipping {$filePath}: could not fetch contents.\n";
|
||||||
@@ -233,7 +233,7 @@ class VersionResetDevCli extends CliFramework
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$putUrl = "{$apiBase}/contents/{$encodedPath}";
|
$putUrl = "{$apiBase}/contents/{$encodedPath}";
|
||||||
$result = $this->giteaApiCall($putUrl, $token, 'PUT', $putBody);
|
$result = $this->gitApiCall($putUrl, $token, 'PUT', $putBody);
|
||||||
|
|
||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
echo "Reset: {$filePath} -> \$this->version = 'development'\n";
|
echo "Reset: {$filePath} -> \$this->version = 'development'\n";
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ class VersionSetPlatformCli extends CliFramework
|
|||||||
// Detect platform — check manifest.xml first, then legacy .mokostandards
|
// Detect platform — check manifest.xml first, then legacy .mokostandards
|
||||||
$platform = '';
|
$platform = '';
|
||||||
|
|
||||||
// New format: .mokogitea/manifest.xml (XML with <platform> tag)
|
// New format: .mokogit/manifest.xml (XML with <platform> tag)
|
||||||
$manifestXml = "{$root}/.mokogitea/manifest.xml";
|
$manifestXml = "{$root}/.mokogit/manifest.xml";
|
||||||
if (file_exists($manifestXml)) {
|
if (file_exists($manifestXml)) {
|
||||||
$xml = @simplexml_load_file($manifestXml);
|
$xml = @simplexml_load_file($manifestXml);
|
||||||
if ($xml && isset($xml->governance->platform)) {
|
if ($xml && isset($xml->governance->platform)) {
|
||||||
@@ -93,7 +93,7 @@ class VersionSetPlatformCli extends CliFramework
|
|||||||
if (empty($platform)) {
|
if (empty($platform)) {
|
||||||
$mokoStandards = "{$root}/.github/.mokostandards";
|
$mokoStandards = "{$root}/.github/.mokostandards";
|
||||||
if (!file_exists($mokoStandards)) {
|
if (!file_exists($mokoStandards)) {
|
||||||
$mokoStandards = "{$root}/.mokogitea/manifest.xml";
|
$mokoStandards = "{$root}/.mokogit/manifest.xml";
|
||||||
}
|
}
|
||||||
if (!file_exists($mokoStandards)) {
|
if (!file_exists($mokoStandards)) {
|
||||||
$mokoStandards = "{$root}/.mokostandards";
|
$mokoStandards = "{$root}/.mokostandards";
|
||||||
@@ -164,8 +164,12 @@ class VersionSetPlatformCli extends CliFramework
|
|||||||
$updated = preg_replace(
|
$updated = preg_replace(
|
||||||
'|<version>[^<]*</version>|',
|
'|<version>[^<]*</version>|',
|
||||||
"<version>{$version}</version>",
|
"<version>{$version}</version>",
|
||||||
$content
|
$content,
|
||||||
|
1
|
||||||
);
|
);
|
||||||
|
if ($updated !== null) {
|
||||||
|
$updated = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $updated);
|
||||||
|
}
|
||||||
if ($updated !== null && $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);
|
||||||
|
|||||||
+5
-5
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/wiki_sync.php
|
* PATH: /cli/wiki_sync.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Sync select wiki pages from mokocli to all template repos
|
* BRIEF: Sync select wiki pages from mokocli to all template repos
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ use MokoCli\CliFramework;
|
|||||||
|
|
||||||
class WikiSyncCli extends CliFramework
|
class WikiSyncCli extends CliFramework
|
||||||
{
|
{
|
||||||
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
private string $gitUrl = 'https://git.mokoconsulting.tech';
|
||||||
private string $token = '';
|
private string $token = '';
|
||||||
private string $org = 'MokoConsulting';
|
private string $org = 'MokoConsulting';
|
||||||
private string $sourceRepo = 'mokocli';
|
private string $sourceRepo = 'mokocli';
|
||||||
@@ -39,7 +39,7 @@ class WikiSyncCli extends CliFramework
|
|||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Sync wiki pages from mokocli to template repos');
|
$this->setDescription('Sync wiki pages from mokocli to template repos');
|
||||||
$this->addArgument('--token', 'Gitea API token (required)', '');
|
$this->addArgument('--token', 'Git API token (required)', '');
|
||||||
$this->addArgument('--org', 'Organization (default: MokoConsulting)', 'MokoConsulting');
|
$this->addArgument('--org', 'Organization (default: MokoConsulting)', 'MokoConsulting');
|
||||||
$this->addArgument('--source', 'Source repo (default: mokocli)', 'mokocli');
|
$this->addArgument('--source', 'Source repo (default: mokocli)', 'mokocli');
|
||||||
$this->addArgument('--target', 'Target repo (can repeat)', '');
|
$this->addArgument('--target', 'Target repo (can repeat)', '');
|
||||||
@@ -195,7 +195,7 @@ class WikiSyncCli extends CliFramework
|
|||||||
|
|
||||||
private function apiGet(string $endpoint): ?array
|
private function apiGet(string $endpoint): ?array
|
||||||
{
|
{
|
||||||
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
|
$url = "{$this->gitUrl}/api/v1{$endpoint}";
|
||||||
$opts = [
|
$opts = [
|
||||||
'http' => [
|
'http' => [
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
@@ -224,7 +224,7 @@ class WikiSyncCli extends CliFramework
|
|||||||
|
|
||||||
private function apiWrite(string $method, string $endpoint, string $payload): ?array
|
private function apiWrite(string $method, string $endpoint, string $payload): ?array
|
||||||
{
|
{
|
||||||
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
|
$url = "{$this->gitUrl}/api/v1{$endpoint}";
|
||||||
$opts = [
|
$opts = [
|
||||||
'http' => [
|
'http' => [
|
||||||
'method' => $method,
|
'method' => $method,
|
||||||
|
|||||||
+54
-114
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: mokocli
|
* INGROUP: mokocli
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /cli/workflow_sync.php
|
* PATH: /cli/workflow_sync.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.44.00
|
||||||
* BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform
|
* BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -26,7 +26,8 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
'joomla' => 'Template-Joomla',
|
'joomla' => 'Template-Joomla',
|
||||||
'dolibarr' => 'Template-Dolibarr',
|
'dolibarr' => 'Template-Dolibarr',
|
||||||
'go' => 'Template-Go',
|
'go' => 'Template-Go',
|
||||||
'mcp' => 'Template-MCP',
|
'npm' => 'Template-NPM',
|
||||||
|
'mcp' => 'Template-NPM', // legacy alias: mcp was consolidated into npm; repo renamed Template-MCP → Template-NPM
|
||||||
'platform' => 'Template-Generic',
|
'platform' => 'Template-Generic',
|
||||||
'generic' => 'Template-Generic',
|
'generic' => 'Template-Generic',
|
||||||
];
|
];
|
||||||
@@ -54,8 +55,8 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->setDescription('Sync workflows from Generic → platform templates → live repos based on manifest.platform');
|
$this->setDescription('Sync workflows from Generic → platform templates → live repos based on manifest.platform');
|
||||||
$this->addArgument('--gitea-url', 'Gitea URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
$this->addArgument('--git-url', 'Git URL (default: https://git.mokoconsulting.tech)', 'https://git.mokoconsulting.tech');
|
||||||
$this->addArgument('--token', 'Gitea API token', '');
|
$this->addArgument('--token', 'Git API token', '');
|
||||||
$this->addArgument('--org', 'Target organization', '');
|
$this->addArgument('--org', 'Target organization', '');
|
||||||
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
||||||
$this->addArgument('--phase', 'Phase to run: all, templates, repos (default: all)', 'all');
|
$this->addArgument('--phase', 'Phase to run: all, templates, repos (default: all)', 'all');
|
||||||
@@ -65,7 +66,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
protected function run(): int
|
protected function run(): int
|
||||||
{
|
{
|
||||||
$giteaUrl = rtrim($this->getArgument('--gitea-url'), '/');
|
$gitUrl = rtrim($this->getArgument('--git-url'), '/');
|
||||||
$token = $this->getArgument('--token');
|
$token = $this->getArgument('--token');
|
||||||
$org = $this->getArgument('--org');
|
$org = $this->getArgument('--org');
|
||||||
$branch = $this->getArgument('--branch');
|
$branch = $this->getArgument('--branch');
|
||||||
@@ -101,7 +102,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
// Phase 1: Sync Generic → Platform Templates
|
// Phase 1: Sync Generic → Platform Templates
|
||||||
if ($phase === 'all' || $phase === 'templates') {
|
if ($phase === 'all' || $phase === 'templates') {
|
||||||
$result = $this->syncGenericToTemplates($giteaUrl, $token, $org, $branch, $platformFilter);
|
$result = $this->syncGenericToTemplates($gitUrl, $token, $org, $branch, $platformFilter);
|
||||||
|
|
||||||
if ($result !== 0) {
|
if ($result !== 0) {
|
||||||
return $result;
|
return $result;
|
||||||
@@ -110,7 +111,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
// Phase 2: Sync Platform Templates → Live Repos
|
// Phase 2: Sync Platform Templates → Live Repos
|
||||||
if ($phase === 'all' || $phase === 'repos') {
|
if ($phase === 'all' || $phase === 'repos') {
|
||||||
$result = $this->syncTemplatesToRepos($giteaUrl, $token, $org, $branch, $platformFilter);
|
$result = $this->syncTemplatesToRepos($gitUrl, $token, $org, $branch, $platformFilter);
|
||||||
|
|
||||||
if ($result !== 0) {
|
if ($result !== 0) {
|
||||||
return $result;
|
return $result;
|
||||||
@@ -129,7 +130,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
* Skips platform-specific overrides (files that exist in the platform template but NOT in Generic).
|
* Skips platform-specific overrides (files that exist in the platform template but NOT in Generic).
|
||||||
*/
|
*/
|
||||||
private function syncGenericToTemplates(
|
private function syncGenericToTemplates(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $branch,
|
string $branch,
|
||||||
@@ -139,7 +140,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
echo "\n";
|
echo "\n";
|
||||||
|
|
||||||
// Get all workflow files from Template-Generic
|
// Get all workflow files from Template-Generic
|
||||||
$genericWorkflows = $this->listWorkflows($giteaUrl, $token, $org, self::GENERIC_TEMPLATE, $branch);
|
$genericWorkflows = $this->listWorkflows($gitUrl, $token, $org, self::GENERIC_TEMPLATE, $branch);
|
||||||
|
|
||||||
if ($genericWorkflows === null) {
|
if ($genericWorkflows === null) {
|
||||||
$this->log('ERROR', 'Could not list workflows from ' . self::GENERIC_TEMPLATE);
|
$this->log('ERROR', 'Could not list workflows from ' . self::GENERIC_TEMPLATE);
|
||||||
@@ -185,12 +186,12 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
$this->skipped++;
|
$this->skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$destPath = '.mokogitea/workflows/' . $filename;
|
$destPath = '.mokogit/workflows/' . $filename;
|
||||||
$label = "{$templateRepo}/{$filename}";
|
$label = "{$templateRepo}/{$filename}";
|
||||||
|
|
||||||
// Get file content from Generic
|
// Get file content from Generic
|
||||||
$sourceContent = $this->getFileContent(
|
$sourceContent = $this->getFileContent(
|
||||||
$giteaUrl, $token, $org,
|
$gitUrl, $token, $org,
|
||||||
self::GENERIC_TEMPLATE, $destPath, $branch
|
self::GENERIC_TEMPLATE, $destPath, $branch
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -203,7 +204,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
$commitMsg = "chore: sync {$filename} from " . self::GENERIC_TEMPLATE . " [skip ci]";
|
$commitMsg = "chore: sync {$filename} from " . self::GENERIC_TEMPLATE . " [skip ci]";
|
||||||
|
|
||||||
$this->pushFile(
|
$this->pushFile(
|
||||||
$giteaUrl, $token, $org, $templateRepo,
|
$gitUrl, $token, $org, $templateRepo,
|
||||||
$destPath, $sourceContent, $branch, $commitMsg, $label
|
$destPath, $sourceContent, $branch, $commitMsg, $label
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -217,7 +218,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
* Phase 2: Sync platform template workflows to live repos based on manifest.platform.
|
* Phase 2: Sync platform template workflows to live repos based on manifest.platform.
|
||||||
*/
|
*/
|
||||||
private function syncTemplatesToRepos(
|
private function syncTemplatesToRepos(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $branch,
|
string $branch,
|
||||||
@@ -226,7 +227,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
$this->log('INFO', '=== Phase 2: Sync Platform Templates → Live Repos ===');
|
$this->log('INFO', '=== Phase 2: Sync Platform Templates → Live Repos ===');
|
||||||
echo "\n";
|
echo "\n";
|
||||||
|
|
||||||
$repos = $this->fetchOrgRepos($giteaUrl, $token, $org);
|
$repos = $this->fetchOrgRepos($gitUrl, $token, $org);
|
||||||
|
|
||||||
if ($repos === null) {
|
if ($repos === null) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -250,7 +251,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read manifest.platform
|
// Read manifest.platform
|
||||||
$platform = $this->getRepoPlatform($giteaUrl, $token, $org, $repoName, $branch);
|
$platform = $this->getRepoPlatform($gitUrl, $token, $org, $repoName, $branch);
|
||||||
|
|
||||||
// Apply platform filter
|
// Apply platform filter
|
||||||
if ($platformFilter !== '' && $platform !== $platformFilter) {
|
if ($platformFilter !== '' && $platform !== $platformFilter) {
|
||||||
@@ -262,11 +263,11 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
// Get workflows from the template (cached)
|
// Get workflows from the template (cached)
|
||||||
if (!isset($templateWorkflowCache[$templateRepo])) {
|
if (!isset($templateWorkflowCache[$templateRepo])) {
|
||||||
$workflows = $this->listWorkflows($giteaUrl, $token, $org, $templateRepo, $branch);
|
$workflows = $this->listWorkflows($gitUrl, $token, $org, $templateRepo, $branch);
|
||||||
|
|
||||||
if ($workflows === null) {
|
if ($workflows === null) {
|
||||||
$this->log('WARN', "Could not list workflows from {$templateRepo}, falling back to " . self::GENERIC_TEMPLATE);
|
$this->log('WARN', "Could not list workflows from {$templateRepo}, falling back to " . self::GENERIC_TEMPLATE);
|
||||||
$workflows = $this->listWorkflows($giteaUrl, $token, $org, self::GENERIC_TEMPLATE, $branch);
|
$workflows = $this->listWorkflows($gitUrl, $token, $org, self::GENERIC_TEMPLATE, $branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
$templateWorkflowCache[$templateRepo] = $workflows ?? [];
|
$templateWorkflowCache[$templateRepo] = $workflows ?? [];
|
||||||
@@ -280,7 +281,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
foreach ($workflows as $workflow) {
|
foreach ($workflows as $workflow) {
|
||||||
$filename = $workflow['name'];
|
$filename = $workflow['name'];
|
||||||
$destPath = '.mokogitea/workflows/' . $filename;
|
$destPath = '.mokogit/workflows/' . $filename;
|
||||||
$label = "{$repoFullName}/{$filename}";
|
$label = "{$repoFullName}/{$filename}";
|
||||||
|
|
||||||
// Skip platform-excluded workflows
|
// Skip platform-excluded workflows
|
||||||
@@ -292,7 +293,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
// Get source content from template
|
// Get source content from template
|
||||||
$sourceContent = $this->getFileContent(
|
$sourceContent = $this->getFileContent(
|
||||||
$giteaUrl, $token, $org,
|
$gitUrl, $token, $org,
|
||||||
$templateRepo, $destPath, $branch
|
$templateRepo, $destPath, $branch
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -305,7 +306,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
$commitMsg = "chore: sync {$filename} from {$templateRepo} [skip ci]";
|
$commitMsg = "chore: sync {$filename} from {$templateRepo} [skip ci]";
|
||||||
|
|
||||||
$this->pushFile(
|
$this->pushFile(
|
||||||
$giteaUrl, $token, $org, $repoName,
|
$gitUrl, $token, $org, $repoName,
|
||||||
$destPath, $sourceContent, $branch, $commitMsg, $label
|
$destPath, $sourceContent, $branch, $commitMsg, $label
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -314,7 +315,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
if ($this->getArgument('--delete-orphans', false)) {
|
if ($this->getArgument('--delete-orphans', false)) {
|
||||||
$templateNames = array_map(fn($w) => $w['name'], $workflows);
|
$templateNames = array_map(fn($w) => $w['name'], $workflows);
|
||||||
$this->deleteOrphanWorkflows(
|
$this->deleteOrphanWorkflows(
|
||||||
$giteaUrl, $token, $org, $repoName, $branch, $templateNames, $platform
|
$gitUrl, $token, $org, $repoName, $branch, $templateNames, $platform
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +328,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
* Push a file to a repo — create or update, skip if identical.
|
* Push a file to a repo — create or update, skip if identical.
|
||||||
*/
|
*/
|
||||||
private function pushFile(
|
private function pushFile(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $repoName,
|
string $repoName,
|
||||||
@@ -338,7 +339,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
string $label
|
string $label
|
||||||
): void {
|
): void {
|
||||||
$existing = $this->apiRequest(
|
$existing = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/"
|
"/api/v1/repos/{$org}/{$repoName}/contents/"
|
||||||
@@ -372,7 +373,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'PUT',
|
'PUT',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/" . $destPath,
|
"/api/v1/repos/{$org}/{$repoName}/contents/" . $destPath,
|
||||||
@@ -400,7 +401,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'POST',
|
'POST',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/" . $destPath,
|
"/api/v1/repos/{$org}/{$repoName}/contents/" . $destPath,
|
||||||
@@ -430,7 +431,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
* - Platform-excluded workflows
|
* - Platform-excluded workflows
|
||||||
*/
|
*/
|
||||||
private function deleteOrphanWorkflows(
|
private function deleteOrphanWorkflows(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $repoName,
|
string $repoName,
|
||||||
@@ -438,7 +439,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
array $templateNames,
|
array $templateNames,
|
||||||
string $platform
|
string $platform
|
||||||
): void {
|
): void {
|
||||||
$repoWorkflows = $this->listWorkflows($giteaUrl, $token, $org, $repoName, $branch);
|
$repoWorkflows = $this->listWorkflows($gitUrl, $token, $org, $repoName, $branch);
|
||||||
if ($repoWorkflows === null) {
|
if ($repoWorkflows === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -468,7 +469,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete orphan
|
// Delete orphan
|
||||||
$filePath = '.mokogitea/workflows/' . $name;
|
$filePath = '.mokogit/workflows/' . $name;
|
||||||
$label = "{$org}/{$repoName}/{$name}";
|
$label = "{$org}/{$repoName}/{$name}";
|
||||||
|
|
||||||
if ($this->dryRun) {
|
if ($this->dryRun) {
|
||||||
@@ -477,7 +478,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$deleted = $this->deleteFile($giteaUrl, $token, $org, $repoName, $filePath, $branch);
|
$deleted = $this->deleteFile($gitUrl, $token, $org, $repoName, $filePath, $branch);
|
||||||
if ($deleted) {
|
if ($deleted) {
|
||||||
fprintf(STDERR, "%-45s | %s\n", $label, 'DELETED');
|
fprintf(STDERR, "%-45s | %s\n", $label, 'DELETED');
|
||||||
$this->deleted++;
|
$this->deleted++;
|
||||||
@@ -489,10 +490,10 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a file from a repo via the Gitea Contents API.
|
* Delete a file from a repo via the Git Contents API.
|
||||||
*/
|
*/
|
||||||
private function deleteFile(
|
private function deleteFile(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $repoName,
|
string $repoName,
|
||||||
@@ -501,7 +502,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
): bool {
|
): bool {
|
||||||
// Get SHA first
|
// Get SHA first
|
||||||
$existing = $this->apiRequest(
|
$existing = $this->apiRequest(
|
||||||
$giteaUrl, $token, 'GET',
|
$gitUrl, $token, 'GET',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}?ref={$branch}"
|
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}?ref={$branch}"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -522,7 +523,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl, $token, 'DELETE',
|
$gitUrl, $token, 'DELETE',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}",
|
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}",
|
||||||
$payload
|
$payload
|
||||||
);
|
);
|
||||||
@@ -531,20 +532,20 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List workflow files in a repo's .mokogitea/workflows/ directory.
|
* List workflow files in a repo's .mokogit/workflows/ directory.
|
||||||
*/
|
*/
|
||||||
private function listWorkflows(
|
private function listWorkflows(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $repoName,
|
string $repoName,
|
||||||
string $branch
|
string $branch
|
||||||
): ?array {
|
): ?array {
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/.mokogitea/workflows?ref={$branch}"
|
"/api/v1/repos/{$org}/{$repoName}/contents/.mokogit/workflows?ref={$branch}"
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($response['code'] !== 200) {
|
if ($response['code'] !== 200) {
|
||||||
@@ -565,7 +566,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
* Get file content from a repo as a raw string.
|
* Get file content from a repo as a raw string.
|
||||||
*/
|
*/
|
||||||
private function getFileContent(
|
private function getFileContent(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $repoName,
|
string $repoName,
|
||||||
@@ -573,7 +574,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
string $branch
|
string $branch
|
||||||
): ?string {
|
): ?string {
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}?ref={$branch}"
|
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}?ref={$branch}"
|
||||||
@@ -593,21 +594,23 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a repo's manifest.xml and extract the platform value.
|
* Read a repo's platform from the MokoGIT metadata API.
|
||||||
* Returns 'generic' if the manifest is missing or has no platform field.
|
* Returns 'generic' if metadata is missing or has no platform field.
|
||||||
|
* (Replaces the retired .mokogit/manifest.xml lookup — platform now lives
|
||||||
|
* in the repo Metadata endpoint: GET /api/v1/repos/{owner}/{repo}/metadata.)
|
||||||
*/
|
*/
|
||||||
private function getRepoPlatform(
|
private function getRepoPlatform(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $org,
|
string $org,
|
||||||
string $repoName,
|
string $repoName,
|
||||||
string $branch
|
string $branch
|
||||||
): string {
|
): string {
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/repos/{$org}/{$repoName}/contents/.mokogitea/manifest.xml?ref={$branch}"
|
"/api/v1/repos/{$org}/{$repoName}/metadata"
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($response['code'] !== 200) {
|
if ($response['code'] !== 200) {
|
||||||
@@ -616,80 +619,17 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
$data = json_decode($response['body'], true);
|
$data = json_decode($response['body'], true);
|
||||||
|
|
||||||
if (!is_array($data) || !isset($data['content'])) {
|
if (!is_array($data) || empty($data['platform'])) {
|
||||||
return 'generic';
|
return 'generic';
|
||||||
}
|
}
|
||||||
|
|
||||||
$xmlContent = base64_decode($data['content']);
|
return strtolower(trim((string) $data['platform']));
|
||||||
|
|
||||||
if ($xmlContent === false || $xmlContent === '') {
|
|
||||||
return 'generic';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suppress XML warnings for malformed manifests
|
|
||||||
$previous = libxml_use_internal_errors(true);
|
|
||||||
$xml = simplexml_load_string($xmlContent);
|
|
||||||
libxml_use_internal_errors($previous);
|
|
||||||
|
|
||||||
if ($xml === false) {
|
|
||||||
return 'generic';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try <governance><platform> (standard location)
|
|
||||||
$platform = '';
|
|
||||||
|
|
||||||
// Register namespace if present
|
|
||||||
$namespaces = $xml->getNamespaces(true);
|
|
||||||
|
|
||||||
if (!empty($namespaces)) {
|
|
||||||
$ns = reset($namespaces);
|
|
||||||
$xml->registerXPathNamespace('mp', $ns);
|
|
||||||
|
|
||||||
$nodes = $xml->xpath('//mp:governance/mp:platform');
|
|
||||||
|
|
||||||
if (!empty($nodes)) {
|
|
||||||
$platform = trim((string) $nodes[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: <identity><platform>
|
|
||||||
if ($platform === '') {
|
|
||||||
$nodes = $xml->xpath('//mp:identity/mp:platform');
|
|
||||||
|
|
||||||
if (!empty($nodes)) {
|
|
||||||
$platform = trim((string) $nodes[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: top-level <platform>
|
|
||||||
if ($platform === '') {
|
|
||||||
$nodes = $xml->xpath('//mp:platform');
|
|
||||||
|
|
||||||
if (!empty($nodes)) {
|
|
||||||
$platform = trim((string) $nodes[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No namespace
|
|
||||||
if (isset($xml->governance->platform)) {
|
|
||||||
$platform = trim((string) $xml->governance->platform);
|
|
||||||
} elseif (isset($xml->identity->platform)) {
|
|
||||||
$platform = trim((string) $xml->identity->platform);
|
|
||||||
} elseif (isset($xml->platform)) {
|
|
||||||
$platform = trim((string) $xml->platform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($platform === '') {
|
|
||||||
return 'generic';
|
|
||||||
}
|
|
||||||
|
|
||||||
return strtolower($platform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all non-archived repos in an org (paginated).
|
* Fetch all non-archived repos in an org (paginated).
|
||||||
*/
|
*/
|
||||||
private function fetchOrgRepos(string $giteaUrl, string $token, string $org): ?array
|
private function fetchOrgRepos(string $gitUrl, string $token, string $org): ?array
|
||||||
{
|
{
|
||||||
$this->log('INFO', "Fetching repos from org: {$org}");
|
$this->log('INFO', "Fetching repos from org: {$org}");
|
||||||
|
|
||||||
@@ -698,7 +638,7 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
$response = $this->apiRequest(
|
$response = $this->apiRequest(
|
||||||
$giteaUrl,
|
$gitUrl,
|
||||||
$token,
|
$token,
|
||||||
'GET',
|
'GET',
|
||||||
"/api/v1/orgs/{$org}/repos?"
|
"/api/v1/orgs/{$org}/repos?"
|
||||||
@@ -740,16 +680,16 @@ class WorkflowSyncCli extends CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an HTTP request to the Gitea API.
|
* Make an HTTP request to the Git API.
|
||||||
*/
|
*/
|
||||||
private function apiRequest(
|
private function apiRequest(
|
||||||
string $giteaUrl,
|
string $gitUrl,
|
||||||
string $token,
|
string $token,
|
||||||
string $method,
|
string $method,
|
||||||
string $endpoint,
|
string $endpoint,
|
||||||
?string $body = null
|
?string $body = null
|
||||||
): array {
|
): array {
|
||||||
$url = $giteaUrl . $endpoint;
|
$url = $gitUrl . $endpoint;
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|||||||
+2
-2
@@ -71,8 +71,8 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "phpunit",
|
"test": "phpunit",
|
||||||
"phpcs": "phpcs --standard=phpcs.xml lib/ validate/ automation/",
|
"phpcs": "phpcs --standard=phpcs.xml lib/ validate/",
|
||||||
"phpstan": "phpstan analyse -c phpstan.neon lib/ validate/ automation/",
|
"phpstan": "phpstan analyse -c phpstan.neon lib/ validate/",
|
||||||
"psalm": "psalm --config=psalm.xml",
|
"psalm": "psalm --config=psalm.xml",
|
||||||
"check": [
|
"check": [
|
||||||
"@phpcs",
|
"@phpcs",
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
* DEFGROUP: MokoCLI.Scripts.Deploy
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /deploy/backup-before-deploy.php
|
* PATH: /deploy/backup-before-deploy.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
/* 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
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
* DEFGROUP: MokoCLI.Scripts.Deploy
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /deploy/deploy-and-verify.php
|
* PATH: /deploy/deploy-and-verify.php
|
||||||
* BRIEF: Deploy with automatic health check and rollback on failure
|
* BRIEF: Deploy with automatic health check and rollback on failure
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
* DEFGROUP: MokoCLI.Scripts.Deploy
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /deploy/deploy-dolibarr.php
|
* PATH: /deploy/deploy-dolibarr.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
* DEFGROUP: MokoCLI.Scripts.Deploy
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /deploy/deploy-joomla.php
|
* PATH: /deploy/deploy-joomla.php
|
||||||
* BRIEF: Smart Joomla deploy — routes files to correct Joomla directories based on XML manifest
|
* BRIEF: Smart Joomla deploy — routes files to correct Joomla directories based on XML manifest
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
* DEFGROUP: MokoCLI.Scripts.Deploy
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /deploy/deploy-sftp.php
|
* PATH: /deploy/deploy-sftp.php
|
||||||
* BRIEF: Deploy a repository src/ directory to a remote web server via SFTP
|
* BRIEF: Deploy a repository src/ directory to a remote web server via SFTP
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
* DEFGROUP: MokoCLI.Scripts.Deploy
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /deploy/health-check.php
|
* PATH: /deploy/health-check.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Post-deploy health check — verify a Joomla site is responding correctly
|
* BRIEF: Post-deploy health check — verify a Joomla site is responding correctly
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* FILE INFORMATION
|
* FILE INFORMATION
|
||||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
* DEFGROUP: MokoCLI.Scripts.Deploy
|
||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoCLI
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
* PATH: /deploy/rollback-joomla.php
|
* PATH: /deploy/rollback-joomla.php
|
||||||
* VERSION: 09.40.00
|
* VERSION: 09.43.00
|
||||||
* BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot
|
* BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user