@mokoconsulting/mcp-mokosuite 1.0.0
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.
- package/.editorconfig +19 -0
- package/.gitattributes +94 -0
- package/.gitmessage +9 -0
- package/.mokogit/ISSUE_TEMPLATE/adr.md +110 -0
- package/.mokogit/ISSUE_TEMPLATE/bug_report.md +48 -0
- package/.mokogit/ISSUE_TEMPLATE/config.yml +18 -0
- package/.mokogit/ISSUE_TEMPLATE/documentation.md +52 -0
- package/.mokogit/ISSUE_TEMPLATE/feature_request.md +51 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_connection_issue.md +69 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_tool_request.md +50 -0
- package/.mokogit/ISSUE_TEMPLATE/question.md +82 -0
- package/.mokogit/ISSUE_TEMPLATE/rfc.md +126 -0
- package/.mokogit/ISSUE_TEMPLATE/security.md +51 -0
- package/.mokogit/ISSUE_TEMPLATE/version.md +24 -0
- package/.mokogit/actions/resolve-source-dir/action.yml +108 -0
- package/.mokogit/workflows/auto-assign.yml +76 -0
- package/.mokogit/workflows/auto-bump.yml +78 -0
- package/.mokogit/workflows/auto-dev-issue.yml +207 -0
- package/.mokogit/workflows/auto-release.yml +596 -0
- package/.mokogit/workflows/branch-cleanup.yml +60 -0
- package/.mokogit/workflows/cascade-dev.yml +198 -0
- package/.mokogit/workflows/changelog-validation.yml +101 -0
- package/.mokogit/workflows/ci-generic.yml +203 -0
- package/.mokogit/workflows/ci-issue-reporter.yml +75 -0
- package/.mokogit/workflows/cleanup.yml +87 -0
- package/.mokogit/workflows/gitleaks.yml +94 -0
- package/.mokogit/workflows/issue-branch.yml +81 -0
- package/.mokogit/workflows/notify.yml +73 -0
- package/.mokogit/workflows/npm-build-test.yml +83 -0
- package/.mokogit/workflows/npm-publish.yml +126 -0
- package/.mokogit/workflows/npm-sdk-check.yml +110 -0
- package/.mokogit/workflows/npm-tool-inventory.yml +80 -0
- package/.mokogit/workflows/pr-branch-check.yml +90 -0
- package/.mokogit/workflows/pr-check.yml +565 -0
- package/.mokogit/workflows/pre-release.yml +413 -0
- package/.mokogit/workflows/push-notify.yml +43 -0
- package/.mokogit/workflows/rc-revert.yml +72 -0
- package/.mokogit/workflows/repo-health.yml +700 -0
- package/.mokogit/workflows/repository-cleanup.yml +525 -0
- package/.mokogit/workflows/standards-compliance.yml +2507 -0
- package/.mokogit/workflows/sync-version-on-merge.yml +130 -0
- package/.mokogit/workflows/version-set.yml +131 -0
- package/CHANGELOG.md +18 -0
- package/CLAUDE.md +52 -0
- package/CODE_OF_CONDUCT.md +70 -0
- package/CONTRIBUTING.md +161 -0
- package/LICENSE +696 -0
- package/Makefile +70 -0
- package/README.md +83 -0
- package/SECURITY.md +34 -0
- package/config.example.json +21 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.js +124 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +53 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +181 -0
- package/dist/signing.d.ts +14 -0
- package/dist/signing.js +62 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +16 -0
- package/docs/API.md +63 -0
- package/docs/ARCHITECTURE.md +73 -0
- package/docs/INSTALLATION.md +102 -0
- package/docs/index.md +12 -0
- package/package.json +35 -0
- package/src/client.ts +142 -0
- package/src/config.ts +63 -0
- package/src/index.ts +336 -0
- package/src/signing.ts +67 -0
- package/src/types.ts +63 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,413 @@
|
|
|
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.Release
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
|
9
|
+
# PATH: /.mokogit/workflows/pre-release.yml
|
|
10
|
+
# VERSION: 06.00.00
|
|
11
|
+
# BRIEF: Batched pre-release on nightly schedule + manual dispatch (rolling dev/rc asset, build-metadata version — #404)
|
|
12
|
+
|
|
13
|
+
name: "Universal: Pre-Release"
|
|
14
|
+
|
|
15
|
+
# #404 — moved OFF every-push to a nightly schedule + manual dispatch. Building a
|
|
16
|
+
# dev/rc pre-release on every push (and committing a version bump back) caused
|
|
17
|
+
# manifest <version> merge conflicts and wasted CI builds + void versioned dev
|
|
18
|
+
# assets. Batching to nightly removes most of that load; the version now advances
|
|
19
|
+
# as BUILD METADATA (version_build_metadata.php) instead of a committed bump, and
|
|
20
|
+
# the asset is published under a FIXED rolling name replaced in place.
|
|
21
|
+
on:
|
|
22
|
+
schedule:
|
|
23
|
+
# Nightly at 07:00 UTC — batches the day's dev merges into one dev build.
|
|
24
|
+
- cron: '0 7 * * *'
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
inputs:
|
|
27
|
+
stability:
|
|
28
|
+
description: 'Pre-release channel'
|
|
29
|
+
required: true
|
|
30
|
+
default: development
|
|
31
|
+
type: choice
|
|
32
|
+
options:
|
|
33
|
+
- development
|
|
34
|
+
- alpha
|
|
35
|
+
- beta
|
|
36
|
+
- release-candidate
|
|
37
|
+
ref:
|
|
38
|
+
description: 'Branch to build (default: dev)'
|
|
39
|
+
required: false
|
|
40
|
+
default: dev
|
|
41
|
+
type: string
|
|
42
|
+
|
|
43
|
+
permissions:
|
|
44
|
+
contents: write
|
|
45
|
+
|
|
46
|
+
env:
|
|
47
|
+
GIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
48
|
+
GIT_ORG: ${{ vars.MOKOGIT_ORG || github.repository_owner }}
|
|
49
|
+
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
build:
|
|
53
|
+
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
|
54
|
+
runs-on: release
|
|
55
|
+
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
|
56
|
+
# Trigger-loop guard (#404 follow-up): this workflow already runs ONLY on `schedule` +
|
|
57
|
+
# `workflow_dispatch` (the every-push trigger that caused the ~150x pre-release storm on
|
|
58
|
+
# chore/mokoonyx-template-sync-* branches was removed in #404) — so a template-sync push
|
|
59
|
+
# can no longer reach it. As defence-in-depth we also refuse a manual dispatch aimed at a
|
|
60
|
+
# bot/sync ref, so provisioning tooling cannot fan out dispatches against those branches.
|
|
61
|
+
if: >-
|
|
62
|
+
!startsWith(github.event.repository.name, 'Template-') &&
|
|
63
|
+
(
|
|
64
|
+
github.event_name == 'schedule' ||
|
|
65
|
+
(
|
|
66
|
+
github.event_name == 'workflow_dispatch' &&
|
|
67
|
+
!startsWith(inputs.ref, 'chore/') &&
|
|
68
|
+
!startsWith(inputs.ref, 'sync/') &&
|
|
69
|
+
!startsWith(inputs.ref, 'bot/') &&
|
|
70
|
+
!startsWith(inputs.ref, 'mokoonyx-template-sync') &&
|
|
71
|
+
!startsWith(inputs.ref, 'template-sync')
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
steps:
|
|
76
|
+
# #404 — no push event any more. The build ref is the dispatch input (default
|
|
77
|
+
# dev) or, for the nightly schedule, dev. On schedule github.ref_name is the
|
|
78
|
+
# default branch (main), so we resolve the pre-release branch explicitly here.
|
|
79
|
+
- name: Resolve build ref
|
|
80
|
+
id: ref
|
|
81
|
+
run: echo "ref=${{ inputs.ref || 'dev' }}" >> "$GITHUB_OUTPUT"
|
|
82
|
+
|
|
83
|
+
- name: Checkout
|
|
84
|
+
uses: actions/checkout@v4
|
|
85
|
+
with:
|
|
86
|
+
fetch-depth: 0
|
|
87
|
+
token: ${{ secrets.MOKOGIT_TOKEN }}
|
|
88
|
+
ref: ${{ steps.ref.outputs.ref }}
|
|
89
|
+
submodules: recursive
|
|
90
|
+
|
|
91
|
+
- name: Update submodules to main
|
|
92
|
+
run: |
|
|
93
|
+
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
|
|
94
|
+
|
|
95
|
+
- name: Setup MokoCLI tools
|
|
96
|
+
env:
|
|
97
|
+
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
98
|
+
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
|
99
|
+
run: |
|
|
100
|
+
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
|
|
101
|
+
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
|
102
|
+
echo Using pre-installed /opt/mokocli
|
|
103
|
+
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
|
104
|
+
else
|
|
105
|
+
echo Falling back to fresh clone
|
|
106
|
+
if ! command -v composer > /dev/null 2>&1; then
|
|
107
|
+
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
|
|
108
|
+
fi
|
|
109
|
+
rm -rf /tmp/mokocli
|
|
110
|
+
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
|
111
|
+
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
|
112
|
+
cd /tmp/mokocli
|
|
113
|
+
# Authenticate to the MokoGIT private Composer registry (git.mokoconsulting.tech)
|
|
114
|
+
# declared in mokocli composer.json (preferred-install=dist) before installing deps.
|
|
115
|
+
composer config -g http-basic.git.mokoconsulting.tech x-access-token "${MOKO_CLONE_TOKEN}"
|
|
116
|
+
composer install --no-dev --no-interaction --no-progress --optimize-autoloader --quiet
|
|
117
|
+
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
- name: Detect platform
|
|
121
|
+
id: platform
|
|
122
|
+
run: |
|
|
123
|
+
# Authoritative source of truth (#122): the repo's org-customizable
|
|
124
|
+
# platform lives in the MokoGIT metadata API. Read it FIRST and, when
|
|
125
|
+
# present, emit it as the single `platform` output. This avoids the
|
|
126
|
+
# previous clobber where platform_detect.php + manifest_read.php both
|
|
127
|
+
# appended `platform=` under this same id (last-write-wins), and where
|
|
128
|
+
# manifest_read.php hit the removed /manifest route (404) and fell back
|
|
129
|
+
# to generic autodetect — misclassifying an mcp repo as nodejs and
|
|
130
|
+
# skipping the whole release.
|
|
131
|
+
PLATFORM=$(curl -sf \
|
|
132
|
+
-H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
|
|
133
|
+
"${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}/metadata" \
|
|
134
|
+
| python3 -c "import json,sys; print((json.load(sys.stdin).get('platform') or '').strip())" 2>/dev/null || true)
|
|
135
|
+
|
|
136
|
+
if [ -n "$PLATFORM" ]; then
|
|
137
|
+
# Authoritative metadata wins — do NOT run the file-based detectors,
|
|
138
|
+
# so nothing can overwrite this value in $GITHUB_OUTPUT.
|
|
139
|
+
echo "platform=${PLATFORM}" >> "$GITHUB_OUTPUT"
|
|
140
|
+
echo "::notice::Platform '${PLATFORM}' from authoritative metadata API"
|
|
141
|
+
else
|
|
142
|
+
# Fallback (metadata empty/unreachable): retain the existing
|
|
143
|
+
# detection so repos without metadata still resolve a platform.
|
|
144
|
+
echo "::notice::No metadata.platform — falling back to file-based detection"
|
|
145
|
+
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
|
146
|
+
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
- name: Check platform eligibility
|
|
150
|
+
id: eligibility
|
|
151
|
+
run: |
|
|
152
|
+
# Shared release engine (#122): the pre-release path runs for every
|
|
153
|
+
# artifact platform that produces releases — joomla, npm, mcp, and client. Deploy-only platforms (go) are
|
|
154
|
+
# EXCLUDED: they consume a tag but never bump/release here.
|
|
155
|
+
#
|
|
156
|
+
# is_joomla_family selects the Joomla/PHP-specific steps (manifest
|
|
157
|
+
# element/zip resolution, release_package.php zip, updates.xml). Those
|
|
158
|
+
# steps stay TRUE for the Joomla family so Joomla behavior is unchanged,
|
|
159
|
+
# and are skipped for npm/mcp/client, which ride only the platform-
|
|
160
|
+
# agnostic steps (version, tag, changelog, release-notes, release,
|
|
161
|
+
# cascade). Build/publish stay in the platform shim (ADR #124).
|
|
162
|
+
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
163
|
+
IS_JOOMLA_FAMILY=false
|
|
164
|
+
# STEP 1 — Authoritative baseline (unchanged): the hardcoded case gate
|
|
165
|
+
# remains the source of truth for `proceed` and the default value of
|
|
166
|
+
# is_joomla_family. Everything below (the registry lookup) is PURELY
|
|
167
|
+
# ADDITIVE and can only *refine* is_joomla_family for a cleanly-matched
|
|
168
|
+
# platform — it never flips `proceed`, never downgrades, and any
|
|
169
|
+
# failure/absence leaves this decision exactly as it is today.
|
|
170
|
+
case "$PLATFORM" in
|
|
171
|
+
joomla*)
|
|
172
|
+
PROCEED=true
|
|
173
|
+
IS_JOOMLA_FAMILY=true
|
|
174
|
+
;;
|
|
175
|
+
npm*|mcp*|client*)
|
|
176
|
+
PROCEED=true
|
|
177
|
+
;;
|
|
178
|
+
*)
|
|
179
|
+
PROCEED=false
|
|
180
|
+
echo "::notice::Platform '$PLATFORM' — not an artifact release platform, skipping pre-release auto-bump"
|
|
181
|
+
;;
|
|
182
|
+
esac
|
|
183
|
+
|
|
184
|
+
# STEP 2 — Registry enhancement (#125): classify the Joomla family from
|
|
185
|
+
# the live platform registry (GET /api/v1/platforms) so newly-registered
|
|
186
|
+
# manifest/packaging platforms get correct family classification without
|
|
187
|
+
# editing this workflow. We key on the registry `family` field: family
|
|
188
|
+
# `joomla` is exactly the Joomla-style manifest/packaging
|
|
189
|
+
# path (the manifest_element + release_package steps). We deliberately do
|
|
190
|
+
# NOT key on `manifest_based`, because npm and mcp are also
|
|
191
|
+
# manifest_based:true yet must ride the platform-agnostic path (family
|
|
192
|
+
# `node`) — so `manifest_based` is ambiguous here and `family` is the
|
|
193
|
+
# clean discriminator.
|
|
194
|
+
#
|
|
195
|
+
# Fail-safe contract: the curl is captured into a var (never allowed to
|
|
196
|
+
# crash the step under set -e -o pipefail), the JSON is parsed with a
|
|
197
|
+
# guarded python3 one-liner, and the result is only applied when it is
|
|
198
|
+
# exactly "true"/"false". On ANY of {non-200, curl error, empty/malformed
|
|
199
|
+
# JSON, parse failure, platform key absent} REGISTRY_FAMILY stays empty
|
|
200
|
+
# and we keep the STEP 1 hardcoded value untouched.
|
|
201
|
+
REGISTRY_JSON="$(curl -sf \
|
|
202
|
+
-H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
|
|
203
|
+
"${GIT_URL}/api/v1/platforms" 2>/dev/null || true)"
|
|
204
|
+
REGISTRY_FAMILY=""
|
|
205
|
+
if [ -n "$REGISTRY_JSON" ]; then
|
|
206
|
+
# Single physical line of python3 so we stay safely inside this YAML
|
|
207
|
+
# block scalar (a column-0 continuation would terminate the scalar).
|
|
208
|
+
# Emits exactly: "true" -> matched key, family joomla;
|
|
209
|
+
# "false" -> matched key, some other family;
|
|
210
|
+
# "" -> key absent (fall back to hardcoded).
|
|
211
|
+
# On malformed/empty JSON the parse raises, 2>/dev/null hides the
|
|
212
|
+
# traceback and the trailing "|| true" keeps set -e -o pipefail from
|
|
213
|
+
# crashing the step, so REGISTRY_FAMILY stays "" and we fall back.
|
|
214
|
+
REGISTRY_FAMILY="$(printf '%s' "$REGISTRY_JSON" | PLATFORM="$PLATFORM" python3 -c 'import json,os,sys; p=os.environ.get("PLATFORM",""); f=next((x.get("family","") for x in json.load(sys.stdin) if x.get("key")==p), None); sys.stdout.write("true" if f == "joomla" else ("false" if f is not None else ""))' 2>/dev/null || true)"
|
|
215
|
+
fi
|
|
216
|
+
if [ "$REGISTRY_FAMILY" = "true" ]; then
|
|
217
|
+
IS_JOOMLA_FAMILY=true
|
|
218
|
+
echo "::notice::Registry classified '$PLATFORM' as Joomla family (via /api/v1/platforms)"
|
|
219
|
+
elif [ "$REGISTRY_FAMILY" = "false" ]; then
|
|
220
|
+
IS_JOOMLA_FAMILY=false
|
|
221
|
+
echo "::notice::Registry classified '$PLATFORM' as non-Joomla family (via /api/v1/platforms)"
|
|
222
|
+
else
|
|
223
|
+
echo "::notice::Registry lookup unavailable/absent for '$PLATFORM' — using hardcoded family classification"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
echo "proceed=${PROCEED}" >> "$GITHUB_OUTPUT"
|
|
227
|
+
echo "is_joomla_family=${IS_JOOMLA_FAMILY}" >> "$GITHUB_OUTPUT"
|
|
228
|
+
if [ "$PROCEED" = "true" ]; then
|
|
229
|
+
echo "::notice::Platform '$PLATFORM' eligible for pre-release (joomla_family=${IS_JOOMLA_FAMILY})"
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
- name: Resolve metadata and bump version
|
|
233
|
+
id: meta
|
|
234
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
235
|
+
run: |
|
|
236
|
+
# #404 — stability comes from the dispatch input; the nightly schedule
|
|
237
|
+
# always builds the development channel. There is no push event.
|
|
238
|
+
if [ "${{ github.event_name }}" = "schedule" ]; then
|
|
239
|
+
STABILITY="development"
|
|
240
|
+
else
|
|
241
|
+
STABILITY="${{ inputs.stability || 'development' }}"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
case "$STABILITY" in
|
|
245
|
+
development) SUFFIX="-dev"; TAG="development" ;;
|
|
246
|
+
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
|
247
|
+
beta) SUFFIX="-beta"; TAG="beta" ;;
|
|
248
|
+
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
|
249
|
+
esac
|
|
250
|
+
|
|
251
|
+
# #404 — VERSION AS BUILD METADATA, NOT A COMMITTED BUMP.
|
|
252
|
+
# version_build_metadata.php derives a monotonic version from the CI run
|
|
253
|
+
# number and stamps it into the in-tree manifest ONLY (no git add/commit/
|
|
254
|
+
# push). This keeps <version> advancing so Joomla's updater still detects
|
|
255
|
+
# new dev/rc builds, while removing the pushed bump commit that caused
|
|
256
|
+
# manifest <version> merge conflicts on every branch->dev merge.
|
|
257
|
+
php ${MOKO_CLI}/version_build_metadata.php \
|
|
258
|
+
--path . --stability "$STABILITY" --github-output 2>/dev/null || true
|
|
259
|
+
|
|
260
|
+
# Read back the derived base (suffix stripped) and cascade into the
|
|
261
|
+
# platform-specific manifest files — still IN-TREE ONLY, never committed.
|
|
262
|
+
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
|
|
263
|
+
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
|
264
|
+
|
|
265
|
+
php ${MOKO_CLI}/version_set_platform.php \
|
|
266
|
+
--path . --version "$VERSION" --branch "${{ steps.ref.outputs.ref }}" --stability "$STABILITY" 2>/dev/null || true
|
|
267
|
+
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
|
268
|
+
|
|
269
|
+
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
|
270
|
+
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
|
271
|
+
|
|
272
|
+
# Append suffix for output
|
|
273
|
+
if [ -n "$SUFFIX" ]; then
|
|
274
|
+
VERSION="${VERSION}${SUFFIX}"
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# NOTE: no commit/push here (#404). The derived version lives only in the
|
|
278
|
+
# built artifact + the dynamic updates feed; the branch tip is untouched.
|
|
279
|
+
|
|
280
|
+
# Auto-detect element via manifest_element.php (Joomla-family only:
|
|
281
|
+
# element/manifest resolution is Joomla-specific). For npm/mcp/client
|
|
282
|
+
# the generic fallback below derives ext_element/zip_name from the repo
|
|
283
|
+
# name — no Joomla manifest is present or required.
|
|
284
|
+
if [ "${{ steps.eligibility.outputs.is_joomla_family }}" = "true" ]; then
|
|
285
|
+
php ${MOKO_CLI}/manifest_element.php \
|
|
286
|
+
--path . --version "$VERSION" --stability "$STABILITY" \
|
|
287
|
+
--repo "${GIT_REPO}" --github-output
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
# Read back element outputs
|
|
291
|
+
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2 || true)
|
|
292
|
+
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2 || true)
|
|
293
|
+
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
294
|
+
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
|
295
|
+
|
|
296
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
297
|
+
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
|
298
|
+
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
|
299
|
+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
300
|
+
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
|
301
|
+
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
|
302
|
+
|
|
303
|
+
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
|
304
|
+
|
|
305
|
+
- name: Create release
|
|
306
|
+
id: release
|
|
307
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
308
|
+
run: |
|
|
309
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
310
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
311
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
312
|
+
php ${MOKO_CLI}/release_create.php \
|
|
313
|
+
--path . --version "$VERSION" --tag "$TAG" \
|
|
314
|
+
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
|
315
|
+
--repo "${GIT_REPO}" --branch "${{ steps.ref.outputs.ref }}" --prerelease
|
|
316
|
+
|
|
317
|
+
- name: Update release notes from CHANGELOG.md
|
|
318
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
319
|
+
run: |
|
|
320
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
321
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
322
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
323
|
+
|
|
324
|
+
# Extract [Unreleased] section via the shared mokocli command (#364 centralization).
|
|
325
|
+
if [ -f "CHANGELOG.md" ]; then
|
|
326
|
+
NOTES=$(php ${MOKO_CLI}/release_notes.php --path . --version Unreleased 2>/dev/null || true)
|
|
327
|
+
# release_notes.php echoes "Release Unreleased" when the section is empty; normalize.
|
|
328
|
+
[ "$NOTES" = "Release Unreleased" ] && NOTES=""
|
|
329
|
+
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
330
|
+
else
|
|
331
|
+
NOTES="Release ${VERSION}"
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# Update release body via API
|
|
335
|
+
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
|
|
336
|
+
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
337
|
+
|
|
338
|
+
if [ -n "$RELEASE_ID" ]; then
|
|
339
|
+
python3 -c "
|
|
340
|
+
import json, urllib.request
|
|
341
|
+
body = open('/dev/stdin').read()
|
|
342
|
+
payload = json.dumps({'body': body}).encode()
|
|
343
|
+
req = urllib.request.Request(
|
|
344
|
+
'${API_BASE}/releases/${RELEASE_ID}',
|
|
345
|
+
data=payload, method='PATCH',
|
|
346
|
+
headers={
|
|
347
|
+
'Authorization': 'token ${{ secrets.MOKOGIT_TOKEN }}',
|
|
348
|
+
'Content-Type': 'application/json'
|
|
349
|
+
})
|
|
350
|
+
urllib.request.urlopen(req)
|
|
351
|
+
" <<< "$NOTES"
|
|
352
|
+
echo "Release notes updated from CHANGELOG.md"
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
# Joomla-family only: mokocli release_package.php builds the extension zip
|
|
356
|
+
# (+ sha256, attach). npm/mcp/client package/publish in their platform shim
|
|
357
|
+
# (trust boundary, ADR #124), so this PHP zip step is skipped for them.
|
|
358
|
+
- name: Build package and upload
|
|
359
|
+
id: package
|
|
360
|
+
if: >-
|
|
361
|
+
steps.eligibility.outputs.proceed == 'true' &&
|
|
362
|
+
steps.eligibility.outputs.is_joomla_family == 'true'
|
|
363
|
+
run: |
|
|
364
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
365
|
+
TAG="${{ steps.meta.outputs.tag }}"
|
|
366
|
+
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
367
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
368
|
+
# #404 — publish the pre-release asset under a FIXED rolling name
|
|
369
|
+
# (pkg_<ext>-dev.zip / -rc.zip) replaced in place, instead of a new
|
|
370
|
+
# versioned file each build. --stability drives the channel slug; the
|
|
371
|
+
# in-package manifest <version> still advances (build metadata) so the
|
|
372
|
+
# updater keeps detecting new builds.
|
|
373
|
+
php ${MOKO_CLI}/release_package.php \
|
|
374
|
+
--path . --version "$VERSION" --tag "$TAG" \
|
|
375
|
+
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
|
376
|
+
--repo "${GIT_REPO}" --output /tmp \
|
|
377
|
+
--stability "$STABILITY" --rolling || true
|
|
378
|
+
|
|
379
|
+
# updates.xml is generated dynamically by MokoGIT license server.
|
|
380
|
+
# #404 — the dynamic feed must point <downloadurl> at the FIXED rolling asset
|
|
381
|
+
# name for dev/rc (pkg_<ext>-dev.zip), matching what release_package.php now
|
|
382
|
+
# publishes. mokocli's updates_xml_build.php grew a --rolling flag for this;
|
|
383
|
+
# the server-side generator (MokoGIT license server) needs the equivalent
|
|
384
|
+
# change so the served updates.xml references the fixed filename, not the old
|
|
385
|
+
# versioned one. See #404 report — flagged for human/server-side follow-up.
|
|
386
|
+
|
|
387
|
+
- name: "Delete lesser pre-release channels (cascade)"
|
|
388
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
389
|
+
continue-on-error: true
|
|
390
|
+
run: |
|
|
391
|
+
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
|
392
|
+
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
|
393
|
+
|
|
394
|
+
php ${MOKO_CLI}/release_cascade.php \
|
|
395
|
+
--stability "${{ steps.meta.outputs.stability }}" \
|
|
396
|
+
--token "${TOKEN}" \
|
|
397
|
+
--api-base "${API_BASE}"
|
|
398
|
+
|
|
399
|
+
- name: Summary
|
|
400
|
+
if: always()
|
|
401
|
+
run: |
|
|
402
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
403
|
+
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
404
|
+
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
|
405
|
+
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
|
406
|
+
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
|
407
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
408
|
+
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
|
409
|
+
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
410
|
+
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
411
|
+
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
|
412
|
+
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
|
413
|
+
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# +========================================================================+
|
|
2
|
+
# | Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> |
|
|
3
|
+
# | SPDX-License-Identifier: GPL-3.0-or-later |
|
|
4
|
+
# | |
|
|
5
|
+
# | BRIEF: Clean, formatted ntfy notification on push to a default branch. |
|
|
6
|
+
# | Issues use an org-level webhook; releases use notify.yml. |
|
|
7
|
+
# +========================================================================+
|
|
8
|
+
name: "Universal: Push Notifications"
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [main, master]
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
env:
|
|
18
|
+
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
|
19
|
+
NTFY_TOKEN: ${{ secrets.NTFY_TOKEN }}
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
push-notify:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- name: Notify push
|
|
26
|
+
# Untrusted values (commit message, actor) passed via env — never
|
|
27
|
+
# interpolated directly into the shell — to avoid command injection.
|
|
28
|
+
env:
|
|
29
|
+
REPO: ${{ github.repository }}
|
|
30
|
+
BRANCH: ${{ github.ref_name }}
|
|
31
|
+
ACTOR: ${{ github.actor }}
|
|
32
|
+
MSG: ${{ github.event.head_commit.message }}
|
|
33
|
+
CLICK: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}
|
|
34
|
+
run: |
|
|
35
|
+
SUMMARY=$(printf '%s\n' "$MSG" | head -n1)
|
|
36
|
+
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
|
|
37
|
+
-H "Title: ${REPO}: push to ${BRANCH}" \
|
|
38
|
+
-H "Tags: package" \
|
|
39
|
+
-H "Priority: default" \
|
|
40
|
+
-H "Click: ${CLICK}" \
|
|
41
|
+
-H "Authorization: Bearer ${NTFY_TOKEN}" \
|
|
42
|
+
-d "${ACTOR}: ${SUMMARY}" \
|
|
43
|
+
"${NTFY_URL}/mokogit-push" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
|
|
@@ -0,0 +1,72 @@
|
|
|
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/Template-Generic
|
|
9
|
+
# PATH: /.mokogit/workflows/rc-revert.yml
|
|
10
|
+
# VERSION: 09.23.00
|
|
11
|
+
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
|
12
|
+
|
|
13
|
+
name: "RC Revert"
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
pull_request:
|
|
17
|
+
types: [closed]
|
|
18
|
+
|
|
19
|
+
env:
|
|
20
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
revert:
|
|
24
|
+
name: Rename rc/ back to dev/
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
if: >-
|
|
27
|
+
github.event.pull_request.merged == false &&
|
|
28
|
+
startsWith(github.event.pull_request.head.ref, 'rc/') &&
|
|
29
|
+
!startsWith(github.event.repository.name, 'Template-')
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- name: Rename branch
|
|
33
|
+
env:
|
|
34
|
+
BRANCH: ${{ github.event.pull_request.head.ref }}
|
|
35
|
+
REPO: ${{ github.repository }}
|
|
36
|
+
GIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
37
|
+
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
38
|
+
run: |
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
|
41
|
+
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
|
42
|
+
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
|
43
|
+
fi
|
|
44
|
+
SUFFIX="${BRANCH#rc/}"
|
|
45
|
+
DEV_BRANCH="dev/${SUFFIX}"
|
|
46
|
+
API="${GIT_URL}/api/v1/repos/${REPO}/branches"
|
|
47
|
+
|
|
48
|
+
# Create dev/ branch from rc/ branch
|
|
49
|
+
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
|
50
|
+
-H "Authorization: token ${TOKEN}" \
|
|
51
|
+
-H "Content-Type: application/json" \
|
|
52
|
+
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
|
53
|
+
"${API}" 2>/dev/null || true)
|
|
54
|
+
if [ "$STATUS" = "201" ]; then
|
|
55
|
+
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
|
56
|
+
else
|
|
57
|
+
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
|
61
|
+
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
|
62
|
+
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
|
63
|
+
-H "Authorization: token ${TOKEN}" \
|
|
64
|
+
"${API}/${ENCODED}" 2>/dev/null || true)
|
|
65
|
+
if [ "$STATUS" = "204" ]; then
|
|
66
|
+
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
|
67
|
+
else
|
|
68
|
+
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
|
72
|
+
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|