@mokoconsulting/mcp-mokosuite 1.0.0 → 1.1.0-dev.372

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,27 +7,20 @@
7
7
  # INGROUP: MokoCLI.Cascade
8
8
  # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
9
  # PATH: /.mokogit/workflows/cascade-dev.yml
10
- # VERSION: 02.01.00
11
- # BRIEF: Cascade main -> dev; auto-merge clean, auto-resolve VERSION-stamp-only conflicts, else notify
10
+ # VERSION: 02.04.00
11
+ # BRIEF: Cascade main -> dev; branch-motivated (no schedule); skips dev-only repos (no main); auto-merge clean, auto-resolve routine conflicts INLINE (manifest version / .mokogit-.gitea-.github / docs-stamps) with no mokocli dependency, fall back to cascade_resolve.php then notify
12
12
 
13
13
  name: "Cascade Main -> Dev"
14
14
 
15
15
  on:
16
+ # Branch-motivated (no time-based schedule): any real push to main cascades to
17
+ # dev immediately. Sync commits from workflow_sync.php carry [skip ci] and are
18
+ # skipped by the platform (they fire no trigger at all), so workflow_sync.php
19
+ # dispatches this workflow directly (workflow_dispatch) after it changes a repo
20
+ # — that is what carries .mokogit drift down to dev, replacing the old daily cron.
16
21
  push:
17
22
  branches:
18
23
  - main
19
- # A sync-only push to main (workflow/CI-config files) should not immediately
20
- # cascade to dev and light up the release pipeline. The daily schedule below
21
- # still carries the .mokogit drift down to dev.
22
- paths-ignore:
23
- - '.mokogit/**'
24
- - '.gitea/**'
25
- - '.github/**'
26
- # Daily safety net: catches drift even when main only received [skip ci] pushes
27
- # (which never fire the push trigger above), and .mokogit-only syncs ignored above.
28
- # Off-round minute to avoid a fleet-wide spike.
29
- schedule:
30
- - cron: '23 7 * * *'
31
24
  workflow_dispatch:
32
25
 
33
26
  permissions:
@@ -51,10 +44,11 @@ jobs:
51
44
  fetch-depth: 0
52
45
  token: ${{ github.token }}
53
46
 
54
- - name: Cascade main -> dev (auto-resolve version stamps, else notify)
47
+ - name: Cascade main -> dev (auto-resolve routine conflicts, else notify)
55
48
  env:
56
49
  TOKEN: ${{ github.token }}
57
50
  REPO: ${{ github.repository }}
51
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
58
52
  run: |
59
53
  set -uo pipefail
60
54
  API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
@@ -66,6 +60,13 @@ jobs:
66
60
  echo "No dev branch - nothing to cascade."; exit 0
67
61
  fi
68
62
 
63
+ # 0b. main must exist too. A dev-only repo (no main branch) has nothing to
64
+ # cascade FROM, so skip immediately instead of spinning up on the daily
65
+ # schedule / manual dispatch and no-op'ing later via a failed compare.
66
+ if ! curl -sf -H "$AUTH" "${API}/branches/main" >/dev/null 2>&1; then
67
+ echo "No main branch (dev-only repo) - nothing to cascade."; exit 0
68
+ fi
69
+
69
70
  # 1. is main ahead of dev?
70
71
  AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
71
72
  | python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
@@ -139,60 +140,127 @@ jobs:
139
140
  CONFLICTS=$(git diff --name-only --diff-filter=U)
140
141
  echo "Conflicted files:"; echo "${CONFLICTS}"
141
142
 
142
- # A conflict is "stamp-only" when every line inside every conflict block matches
143
- # a version-stamp pattern (VERSION: header, <version> element, or CHANGELOG title).
144
- is_stamp_only() {
145
- awk '
146
- /^<<<<<<< / { inc=1; next }
147
- inc && /^=======$/ { next }
148
- /^>>>>>>> / { inc=0; next }
149
- inc { if ($0 !~ /(VERSION:|<version>|# Changelog)/) { bad=1 } }
150
- END { exit(bad ? 1 : 0) }
151
- ' "$1"
152
- }
153
- # Resolve a stamp-only file by keeping dev (ours) for the conflicting lines,
154
- # preserving all auto-merged content around them.
155
- keep_ours() {
156
- awk '
157
- /^<<<<<<< / { inc=1; side="ours"; next }
158
- inc && /^=======$/ { side="theirs"; next }
159
- /^>>>>>>> / { inc=0; next }
160
- { if (!inc) { print; next } if (side=="ours") print }
161
- ' "$1" > "$1.resolved" && mv "$1.resolved" "$1"
143
+ # --- Inline resolver (pure bash/git, NO mokocli dependency) -------------
144
+ # The common cascade churn is routine and deterministic, so resolve it here
145
+ # BEFORE reaching for cascade_resolve.php. This is what keeps the version
146
+ # ladder moving even when mokocli is unreachable on an ephemeral runner
147
+ # (the failure mode this workflow was hardened against): a pure
148
+ # version-stamp conflict must never stall dev behind main.
149
+ #
150
+ # * manifest <version> conflicts (*.xml containing <extension) ->
151
+ # version = max(dev, main) via `sort -V`, rewriting the tag while
152
+ # preserving the file's own indentation (repos MIX tab and 2-space).
153
+ # * .mokogit/** .gitea/** .github/** -> take main/canonical (--theirs).
154
+ # * docs / CHANGELOG / stamp files -> keep dev (--ours).
155
+ # Anything still conflicted afterwards falls through to cascade_resolve.php
156
+ # (if available) and finally to a human, exactly as before.
157
+ resolve_manifest_version() {
158
+ # $1 = path to a conflicted *.xml manifest. Resolves ONLY the <version>
159
+ # conflict to max(dev,main); returns 0 if fully resolved, 1 otherwise.
160
+ local f="$1"
161
+ # Bail if the conflict touches anything other than a single <version> line
162
+ # on each side (i.e. a real content conflict, not just the stamp).
163
+ local nonver
164
+ nonver=$(awk '
165
+ /^<<<<<<< /{c=1;next} /^=======$/{c=2;next} /^>>>>>>> /{c=0;next}
166
+ (c==1||c==2) && $0 !~ /<version>[^<]*<\/version>/ {print "X"}
167
+ ' "$f")
168
+ [ -n "$nonver" ] && return 1
169
+ # Collect the version value from each side of every conflict hunk.
170
+ local ours theirs maxv
171
+ ours=$(awk '
172
+ /^<<<<<<< /{c=1;next} /^=======$/{c=2;next} /^>>>>>>> /{c=0;next}
173
+ c==1' "$f" | sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' | head -n1)
174
+ theirs=$(awk '
175
+ /^<<<<<<< /{c=1;next} /^=======$/{c=2;next} /^>>>>>>> /{c=0;next}
176
+ c==2' "$f" | sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' | head -n1)
177
+ [ -z "$ours" ] && [ -z "$theirs" ] && return 1
178
+ maxv=$(printf '%s\n%s\n' "$ours" "$theirs" | grep -v '^$' | sort -V | tail -n1)
179
+ [ -z "$maxv" ] && return 1
180
+ # Rewrite: drop conflict markers, keep OUR side's <version> line but with
181
+ # the max value substituted (preserves this file's leading indentation,
182
+ # tab or spaces). Drop the theirs-side version line.
183
+ awk -v maxv="$maxv" '
184
+ /^<<<<<<< /{c=1;next}
185
+ /^=======$/{c=2;next}
186
+ /^>>>>>>> /{c=0;next}
187
+ {
188
+ if (c==2) next
189
+ if (($0 ~ /<version>[^<]*<\/version>/) && (c==1)) {
190
+ sub(/<version>[^<]*<\/version>/, "<version>" maxv "</version>")
191
+ }
192
+ print
193
+ }' "$f" > "${f}.resolved" && mv "${f}.resolved" "$f"
194
+ # Fully resolved only if no markers remain.
195
+ ! grep -qE '^(<<<<<<< |=======$|>>>>>>> )' "$f"
162
196
  }
163
197
 
164
- ALL_STAMP=1
165
198
  for f in ${CONFLICTS}; do
166
- if ! is_stamp_only "$f"; then
167
- echo "::notice::$f has non-stamp conflicts -> manual resolution required."
168
- ALL_STAMP=0; break
169
- fi
199
+ case "$f" in
200
+ .mokogit/*|.gitea/*|.github/*)
201
+ git checkout --theirs -- "$f" && git add -- "$f" \
202
+ && echo "resolved (theirs/main): $f" ;;
203
+ *CHANGELOG*|*/docs/*|docs/*|*.md|*VERSION*|*.stamp)
204
+ git checkout --ours -- "$f" && git add -- "$f" \
205
+ && echo "resolved (ours/dev): $f" ;;
206
+ *.xml)
207
+ if grep -q '<extension' "$f" 2>/dev/null && resolve_manifest_version "$f"; then
208
+ git add -- "$f"; echo "resolved (manifest version=max): $f"
209
+ else
210
+ echo "unresolved (xml, not a simple version stamp): $f"
211
+ fi ;;
212
+ *)
213
+ echo "unresolved (needs deeper resolver): $f" ;;
214
+ esac
170
215
  done
171
216
 
172
- if [ "$ALL_STAMP" != "1" ]; then
173
- git merge --abort || true
174
- notify "has non-version-stamp conflicts and cannot be auto-resolved."
217
+ # If the inline pass cleared every conflict, commit + push and we are done —
218
+ # no mokocli needed.
219
+ if [ -z "$(git diff --name-only --diff-filter=U)" ]; then
220
+ git commit -m "chore(sync): cascade main -> dev (auto-resolved inline) [skip ci]" >/dev/null
221
+ git push origin dev
222
+ echo "Cascade auto-resolved inline (no mokocli) and pushed to dev."
223
+ curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
224
+ -d '{"body":"Cascade auto-resolved inline (manifest version=max(dev,main), .mokogit/.gitea/.github -> main, docs/stamps -> dev) with no mokocli dependency, and pushed to dev. Closing."}' >/dev/null || true
225
+ curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
226
+ -d '{"state":"closed"}' >/dev/null || true
175
227
  exit 0
176
228
  fi
177
229
 
178
- echo "All conflicts are VERSION-stamp-only; resolving in favour of dev."
179
- for f in ${CONFLICTS}; do
180
- keep_ours "$f"
181
- git add "$f"
182
- done
230
+ echo "Inline pass left conflicts; falling back to cascade_resolve.php..."
231
+ echo "Still conflicted:"; git diff --name-only --diff-filter=U
183
232
 
184
- # Best-effort: normalise stamps to dev's version if mokocli is available.
185
- if [ -f /opt/mokocli/cli/version_check.php ]; then
186
- php /opt/mokocli/cli/version_check.php --fix || true
187
- git add -A
233
+ # --- Fallback: cascade_resolve.php (existing behaviour) -----------------
234
+ # Handles the messier cases (modify/delete, plugin renames, etc.). It BAILS
235
+ # (exit 2) on real code conflicts, so those still get a human. ubuntu-latest
236
+ # has no /opt/mokocli, so clone mokocli on demand.
237
+ RESOLVER=/opt/mokocli/cli/cascade_resolve.php
238
+ if [ ! -f "$RESOLVER" ]; then
239
+ if git clone --depth 1 --quiet \
240
+ "https://x-access-token:${MOKOGIT_TOKEN}@${MOKOGIT_URL#https://}/MokoConsulting/mokocli.git" \
241
+ /tmp/mokocli 2>/dev/null \
242
+ || git clone --depth 1 --quiet "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli 2>/dev/null; then
243
+ RESOLVER=/tmp/mokocli/cli/cascade_resolve.php
244
+ else
245
+ # Do NOT swallow this silently: a degraded run must be visible so the
246
+ # operator knows the fallback resolver was unavailable.
247
+ echo "::warning::On-demand mokocli clone failed; cascade_resolve.php is UNAVAILABLE. Inline resolver handled the routine classes; any remaining conflicts need a human."
248
+ RESOLVER=/tmp/mokocli/cli/cascade_resolve.php
249
+ fi
188
250
  fi
189
251
 
190
- git commit -m "chore(sync): cascade main -> dev (auto-resolved version stamps) [skip ci]" >/dev/null
191
- git push origin dev
192
- echo "Cascade auto-resolved and pushed to dev."
252
+ if [ -f "$RESOLVER" ] && php "$RESOLVER" --path . --theirs-ref origin/main; then
253
+ git commit -m "chore(sync): cascade main -> dev (auto-resolved) [skip ci]" >/dev/null
254
+ git push origin dev
255
+ echo "Cascade auto-resolved and pushed to dev."
256
+ curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
257
+ -d '{"body":"cascade_resolve auto-resolved the merge (manifests/workflows/docs/stamps, modify-delete) and pushed to dev. Closing."}' >/dev/null || true
258
+ curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
259
+ -d '{"state":"closed"}' >/dev/null || true
260
+ exit 0
261
+ fi
193
262
 
194
- # Close the now-redundant PR (its changes are in dev) with an explanatory comment.
195
- curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
196
- -d '{"body":"Auto-resolved VERSION-stamp-only conflicts and pushed the merge to dev. Closing."}' >/dev/null || true
197
- curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
198
- -d '{"state":"closed"}' >/dev/null || true
263
+ # Resolver unavailable, or it bailed on a real (code) conflict -> human.
264
+ git merge --abort || true
265
+ notify "has code conflicts cascade_resolve could not safely auto-resolve."
266
+ exit 0
@@ -9,7 +9,7 @@
9
9
  # INGROUP: MokoStandards.CI
10
10
  # REPO: https://github.com/mokoconsulting-tech/MokoStandards
11
11
  # PATH: /templates/workflows/shared/changelog-validation.yml.template
12
- # VERSION: 04.06.00
12
+ # VERSION: 04.06.01
13
13
  # BRIEF: Validates CHANGELOG.md format and version consistency
14
14
  # NOTE: Deployed to .mokogit/workflows/changelog-validation.yml in governed repos.
15
15
 
@@ -30,6 +30,11 @@ permissions:
30
30
  env:
31
31
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
32
32
 
33
+ concurrency:
34
+ # storm-reduction: collapse superseded validation runs on the same ref
35
+ group: ${{ github.workflow }}-${{ github.ref }}
36
+ cancel-in-progress: true
37
+
33
38
  jobs:
34
39
  validate-changelog:
35
40
  name: Validate CHANGELOG.md
@@ -41,61 +46,72 @@ jobs:
41
46
 
42
47
  - name: Check CHANGELOG.md exists
43
48
  run: |
44
- echo "### Changelog Validation" >> $GITHUB_STEP_SUMMARY
49
+ echo "### Changelog Validation" | tee -a $GITHUB_STEP_SUMMARY
45
50
  if [ ! -f "CHANGELOG.md" ]; then
46
- echo "CHANGELOG.md not found in repository root." >> $GITHUB_STEP_SUMMARY
51
+ echo "CHANGELOG.md not found in repository root." | tee -a $GITHUB_STEP_SUMMARY
47
52
  exit 1
48
53
  fi
49
- echo "CHANGELOG.md exists." >> $GITHUB_STEP_SUMMARY
54
+ echo "CHANGELOG.md exists." | tee -a $GITHUB_STEP_SUMMARY
50
55
 
51
- - name: Check VERSION header matches README.md
56
+ - name: Check VERSION header matches README.md (only if README declares one)
52
57
  run: |
53
- # Extract version from README.md FILE INFORMATION block
54
- README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
58
+ # A FILE INFORMATION VERSION block is optional in README.md — public-facing
59
+ # READMEs typically have none. Only enforce a CHANGELOG match when README
60
+ # actually declares a version; otherwise skip (not a failure).
61
+ # `|| true`: under the default `bash -e -o pipefail`, a grep with no match
62
+ # exits 1 and pipefail propagates it, which would abort the step BEFORE the
63
+ # `-z` skip below — the exact common case (README with no VERSION block). Keep
64
+ # the empty result non-fatal so the intended skip path stays reachable.
65
+ README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]+\.[0-9]+\.[0-9]+' README.md 2>/dev/null | head -1 || true)
55
66
  if [ -z "$README_VERSION" ]; then
56
- echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
57
- exit 1
67
+ echo "README.md has no FILE INFORMATION VERSION block — skipping version-match check." | tee -a $GITHUB_STEP_SUMMARY
68
+ exit 0
58
69
  fi
59
70
 
60
- # Check that CHANGELOG.md has a matching version header
61
- CHANGELOG_VERSION=$(grep -oP '^\#\#\s*\[\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' CHANGELOG.md | head -1)
71
+ CHANGELOG_VERSION=$(grep -oP '^\#\#\s*\[\K[0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.md | head -1 || true)
62
72
  if [ -z "$CHANGELOG_VERSION" ]; then
63
- echo "No version header found in CHANGELOG.md (expected \`## [XX.YY.ZZ] - YYYY-MM-DD\`)." >> $GITHUB_STEP_SUMMARY
73
+ echo "README declares VERSION \`${README_VERSION}\` but CHANGELOG.md has no matching \`## [X.Y.Z]\` header." | tee -a $GITHUB_STEP_SUMMARY
64
74
  exit 1
65
75
  fi
66
76
 
67
77
  if [ "$CHANGELOG_VERSION" != "$README_VERSION" ]; then
68
- echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
78
+ echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." | tee -a $GITHUB_STEP_SUMMARY
69
79
  exit 1
70
80
  fi
71
81
 
72
- echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." >> $GITHUB_STEP_SUMMARY
82
+ echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." | tee -a $GITHUB_STEP_SUMMARY
73
83
 
74
84
  - name: Validate conventional changelog format
75
85
  run: |
76
86
  ERRORS=0
77
87
 
78
- # Check that version entries follow ## [XX.YY.ZZ] - YYYY-MM-DD format
88
+ # Validate the format of each version entry. `## [Unreleased]` is a valid
89
+ # Keep-a-Changelog section (not a dated version) and is skipped.
79
90
  while IFS= read -r LINE; do
80
- if ! echo "$LINE" | grep -qP '^\#\#\s*\[[0-9]{2}\.[0-9]{2}\.[0-9]{2}\]\s*-\s*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
81
- echo "Malformed version header: \`${LINE}\`" >> $GITHUB_STEP_SUMMARY
82
- echo " Expected format: \`## [XX.YY.ZZ] - YYYY-MM-DD\`" >> $GITHUB_STEP_SUMMARY
91
+ if echo "$LINE" | grep -qiE '^\#\#[[:space:]]*\[unreleased\]'; then
92
+ continue
93
+ fi
94
+ if ! echo "$LINE" | grep -qP '^\#\#\s*\[[0-9]+\.[0-9]+\.[0-9]+\]\s*[-–—]+\s*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
95
+ echo "Malformed version header: \`${LINE}\`" | tee -a $GITHUB_STEP_SUMMARY
96
+ echo " Expected format: \`## [X.Y.Z] --- YYYY-MM-DD\` (any hyphen/dash separator)" | tee -a $GITHUB_STEP_SUMMARY
83
97
  ERRORS=$((ERRORS + 1))
84
98
  fi
85
99
  done < <(grep -P '^\#\#\s*\[' CHANGELOG.md)
86
100
 
87
- ENTRY_COUNT=$(grep -cP '^\#\#\s*\[' CHANGELOG.md || echo "0")
88
- if [ "$ENTRY_COUNT" -eq 0 ]; then
89
- echo "No version entries found in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
101
+ # Count real (dated) version entries, excluding the Unreleased section.
102
+ VERSION_COUNT=$(grep -P '^\#\#\s*\[' CHANGELOG.md | { grep -viE '\[unreleased\]' || true; } | wc -l | tr -d ' ')
103
+ HAS_UNRELEASED=$(grep -ciE '^\#\#[[:space:]]*\[unreleased\]' CHANGELOG.md | tr -d ' ')
104
+ if [ "$VERSION_COUNT" -eq 0 ] && [ "$HAS_UNRELEASED" -eq 0 ]; then
105
+ echo "No version entries or Unreleased section found in CHANGELOG.md." | tee -a $GITHUB_STEP_SUMMARY
90
106
  ERRORS=$((ERRORS + 1))
91
107
  else
92
- echo "Found ${ENTRY_COUNT} version entr(ies) in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
108
+ echo "Found ${VERSION_COUNT} released version entr(ies)$([ "$HAS_UNRELEASED" -gt 0 ] && echo ' + an Unreleased section') in CHANGELOG.md." | tee -a $GITHUB_STEP_SUMMARY
93
109
  fi
94
110
 
95
- echo "" >> $GITHUB_STEP_SUMMARY
111
+ echo "" | tee -a $GITHUB_STEP_SUMMARY
96
112
  if [ "${ERRORS}" -gt 0 ]; then
97
- echo "**${ERRORS} format issue(s) found.**" >> $GITHUB_STEP_SUMMARY
113
+ echo "**${ERRORS} format issue(s) found.**" | tee -a $GITHUB_STEP_SUMMARY
98
114
  exit 1
99
115
  else
100
- echo "**Changelog format validation passed.**" >> $GITHUB_STEP_SUMMARY
116
+ echo "**Changelog format validation passed.**" | tee -a $GITHUB_STEP_SUMMARY
101
117
  fi
@@ -53,7 +53,7 @@ jobs:
53
53
  MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
54
54
  run: |
55
55
  git clone --depth 1 --filter=blob:none --sparse "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
56
- cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
56
+ cd /tmp/mokocli && git sparse-checkout set cli
57
57
 
58
58
  - name: Report CI failure
59
59
  env:
@@ -33,11 +33,11 @@ jobs:
33
33
  uses: actions/checkout@v4
34
34
  with:
35
35
  fetch-depth: 0
36
- token: ${{ secrets.MOKOGIT_TOKEN }}
36
+ token: ${{ secrets.MOKOGIT_TOKEN || github.token }}
37
37
 
38
38
  - name: Delete merged branches
39
39
  env:
40
- MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
40
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
41
41
  run: |
42
42
  echo "=== Merged Branch Cleanup ==="
43
43
  API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
@@ -66,7 +66,7 @@ jobs:
66
66
 
67
67
  - name: Clean old workflow runs
68
68
  env:
69
- MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
69
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
70
70
  run: |
71
71
  echo "=== Workflow Run Cleanup ==="
72
72
  API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
@@ -0,0 +1,83 @@
1
+ # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+ #
5
+ # FILE INFORMATION
6
+ # DEFGROUP: MokoGIT.Workflow
7
+ # INGROUP: MokoCLI.Universal
8
+ # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
9
+ # PATH: /.mokogit/workflows/workflow-sync-trigger.yml
10
+ # VERSION: 01.01.01
11
+ # BRIEF: Trigger workflow sync to live repos when a PR is merged to main
12
+
13
+ name: "Universal: Workflow Sync Trigger"
14
+
15
+ on:
16
+ workflow_dispatch:
17
+ pull_request:
18
+ types: [closed]
19
+ branches:
20
+ - main
21
+
22
+ env:
23
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
24
+
25
+ jobs:
26
+ sync:
27
+ name: Sync workflows to live repos
28
+ runs-on: release
29
+ if: >-
30
+ startsWith(github.event.repository.name, 'Template-') &&
31
+ (github.event_name == 'workflow_dispatch' ||
32
+ (github.event.pull_request.merged == true &&
33
+ !contains(github.event.pull_request.title, '[skip sync]')))
34
+
35
+ steps:
36
+ - name: Determine platform from repo name
37
+ id: platform
38
+ run: |
39
+ REPO="${{ github.event.repository.name }}"
40
+ case "$REPO" in
41
+ Template-Joomla) PLATFORM="joomla" ;;
42
+ Template-Dolibarr) PLATFORM="dolibarr" ;;
43
+ Template-Go) PLATFORM="go" ;;
44
+ Template-NPM) PLATFORM="npm" ;;
45
+ Template-Generic) PLATFORM="" ;;
46
+ *) PLATFORM="" ;;
47
+ esac
48
+ echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
49
+ echo "Platform: ${PLATFORM:-all}"
50
+
51
+ - name: Clone MokoCLI
52
+ env:
53
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
54
+ run: |
55
+ MOKOGIT_URL="${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}"
56
+ git clone --depth 1 "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
57
+
58
+ - name: Install PHP
59
+ run: |
60
+ if ! command -v php &> /dev/null; then
61
+ apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
62
+ fi
63
+
64
+ - name: Install dependencies
65
+ run: |
66
+ cd /tmp/mokocli
67
+ composer install --no-dev --no-interaction --quiet 2>/dev/null || true
68
+
69
+ - name: Run workflow sync
70
+ env:
71
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
72
+ run: |
73
+ ARGS="--token ${MOKOGIT_TOKEN}"
74
+ ARGS="${ARGS} --org ${{ vars.MOKOGIT_ORG || github.repository_owner }}"
75
+ ARGS="${ARGS} --phase repos"
76
+ ARGS="${ARGS} --delete-orphans"
77
+
78
+ PLATFORM="${{ steps.platform.outputs.platform }}"
79
+ if [ -n "$PLATFORM" ]; then
80
+ ARGS="${ARGS} --platform-filter ${PLATFORM}"
81
+ fi
82
+
83
+ php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
@@ -32,7 +32,7 @@ jobs:
32
32
  # injection via a crafted issue title). Pass everything through env so
33
33
  # the values arrive as ordinary shell variables that are not re-parsed.
34
34
  env:
35
- MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
35
+ MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
36
36
  REPO: ${{ github.repository }}
37
37
  ISSUE_NUM: ${{ github.event.issue.number }}
38
38
  ISSUE_TITLE: ${{ github.event.issue.title }}