@mokoconsulting/mcp-mokogitea-api 1.2.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/.gitattributes +94 -0
- package/.gitmessage +9 -0
- package/.mokogitea/ISSUE_TEMPLATE/adr.md +110 -0
- package/.mokogitea/ISSUE_TEMPLATE/bug_report.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/config.yml +18 -0
- package/.mokogitea/ISSUE_TEMPLATE/documentation.md +52 -0
- package/.mokogitea/ISSUE_TEMPLATE/enterprise_support.md +85 -0
- package/.mokogitea/ISSUE_TEMPLATE/feature_request.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/firewall-request.md +190 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +67 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_tool_request.md +49 -0
- package/.mokogitea/ISSUE_TEMPLATE/question.md +82 -0
- package/.mokogitea/ISSUE_TEMPLATE/rfc.md +126 -0
- package/.mokogitea/ISSUE_TEMPLATE/security.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/version.md +24 -0
- package/.mokogitea/auto-assign.yml +76 -0
- package/.mokogitea/auto-dev-issue.yml +207 -0
- package/.mokogitea/auto-release.yml +337 -0
- package/.mokogitea/branch-protection.yml +251 -0
- package/.mokogitea/changelog-validation.yml +101 -0
- package/.mokogitea/codeql-analysis.yml +115 -0
- package/.mokogitea/copilot-agent.yml +44 -0
- package/.mokogitea/deploy-demo.yml +734 -0
- package/.mokogitea/deploy-dev.yml +700 -0
- package/.mokogitea/enterprise-firewall-setup.yml +758 -0
- package/.mokogitea/manifest.xml +25 -0
- package/.mokogitea/mcp-auto-release.yml +278 -0
- package/.mokogitea/mcp-build-test.yml +65 -0
- package/.mokogitea/mcp-sdk-check.yml +109 -0
- package/.mokogitea/mcp-tool-inventory.yml +61 -0
- package/.mokogitea/pr-branch-check.yml +90 -0
- package/.mokogitea/repository-cleanup.yml +525 -0
- package/.mokogitea/standards-compliance.yml +2614 -0
- package/.mokogitea/sync-version-on-merge.yml +133 -0
- package/.mokogitea/workflows/auto-assign.yml +76 -0
- package/.mokogitea/workflows/auto-bump.yml +66 -0
- package/.mokogitea/workflows/auto-dev-issue.yml +207 -0
- package/.mokogitea/workflows/auto-release.yml +341 -0
- package/.mokogitea/workflows/branch-cleanup.yml +48 -0
- package/.mokogitea/workflows/cascade-dev.yml +10 -0
- package/.mokogitea/workflows/changelog-validation.yml +101 -0
- package/.mokogitea/workflows/ci-generic.yml +204 -0
- package/.mokogitea/workflows/cleanup.yml +87 -0
- package/.mokogitea/workflows/codeql-analysis.yml +115 -0
- package/.mokogitea/workflows/copilot-agent.yml +44 -0
- package/.mokogitea/workflows/deploy-manual.yml +126 -0
- package/.mokogitea/workflows/enterprise-firewall-setup.yml +758 -0
- package/.mokogitea/workflows/gitleaks.yml +96 -0
- package/.mokogitea/workflows/issue-branch.yml +73 -0
- package/.mokogitea/workflows/mcp-auto-release.yml +280 -0
- package/.mokogitea/workflows/mcp-build-test.yml +65 -0
- package/.mokogitea/workflows/mcp-sdk-check.yml +109 -0
- package/.mokogitea/workflows/mcp-tool-inventory.yml +61 -0
- package/.mokogitea/workflows/notify.yml +70 -0
- package/.mokogitea/workflows/npm-publish.yml +51 -0
- package/.mokogitea/workflows/pr-check.yml +508 -0
- package/.mokogitea/workflows/pre-release.yml +11 -0
- package/.mokogitea/workflows/repo-health.yml +711 -0
- package/.mokogitea/workflows/repository-cleanup.yml +525 -0
- package/.mokogitea/workflows/security-audit.yml +82 -0
- package/.mokogitea/workflows/standards-compliance.yml +2614 -0
- package/.mokogitea/workflows/sync-version-on-merge.yml +130 -0
- package/.mokogitea/workflows/update-server.yml +312 -0
- package/CHANGELOG.md +145 -0
- package/CLAUDE.md +43 -0
- package/CONTRIBUTING.md +161 -0
- package/README.md +286 -0
- package/SECURITY.md +91 -0
- package/automation/ci-issue-reporter.sh +237 -0
- package/config.example.json +13 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.js +104 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +48 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1119 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +16 -0
- package/package.json +34 -0
- package/scripts/setup.mjs +40 -0
- package/src/client.ts +120 -0
- package/src/config.ts +58 -0
- package/src/index.ts +1712 -0
- package/src/types.ts +37 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,337 @@
|
|
|
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: GitHub.Workflow
|
|
7
|
+
# INGROUP: MokoStandards.Release
|
|
8
|
+
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
|
9
|
+
# PATH: /templates/workflows/shared/auto-release.yml.template
|
|
10
|
+
# VERSION: 04.06.00
|
|
11
|
+
# BRIEF: Generic build & release pipeline — version branch, platform version, badges, tag, release
|
|
12
|
+
#
|
|
13
|
+
# +========================================================================+
|
|
14
|
+
# | BUILD & RELEASE PIPELINE |
|
|
15
|
+
# +========================================================================+
|
|
16
|
+
# | |
|
|
17
|
+
# | Triggers on push to main (skips bot commits + [skip ci]): |
|
|
18
|
+
# | |
|
|
19
|
+
# | Every push: |
|
|
20
|
+
# | 1. Read version from README.md |
|
|
21
|
+
# | 3. Set platform version |
|
|
22
|
+
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
|
23
|
+
# | 6. Create git tag vXX.YY.ZZ |
|
|
24
|
+
# | 7a. Patch: update existing GitHub Release for this minor |
|
|
25
|
+
# | |
|
|
26
|
+
# | Every version change: archives main -> version/XX.YY branch |
|
|
27
|
+
# | Patch 00 = development (no release). First release = patch 01. |
|
|
28
|
+
# | First release only (patch == 01): |
|
|
29
|
+
# | 7b. Create new GitHub Release |
|
|
30
|
+
# | |
|
|
31
|
+
# +========================================================================+
|
|
32
|
+
|
|
33
|
+
name: Build & Release
|
|
34
|
+
|
|
35
|
+
on:
|
|
36
|
+
push:
|
|
37
|
+
branches:
|
|
38
|
+
- main
|
|
39
|
+
- master
|
|
40
|
+
paths:
|
|
41
|
+
- 'src/**'
|
|
42
|
+
- 'htdocs/**'
|
|
43
|
+
|
|
44
|
+
env:
|
|
45
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
46
|
+
|
|
47
|
+
permissions:
|
|
48
|
+
contents: write
|
|
49
|
+
|
|
50
|
+
jobs:
|
|
51
|
+
release:
|
|
52
|
+
name: Build & Release Pipeline
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
if: >-
|
|
55
|
+
!contains(github.event.head_commit.message, '[skip ci]') &&
|
|
56
|
+
github.actor != 'github-actions[bot]'
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
- name: Checkout repository
|
|
60
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
61
|
+
with:
|
|
62
|
+
token: ${{ secrets.GH_TOKEN || github.token }}
|
|
63
|
+
fetch-depth: 0
|
|
64
|
+
|
|
65
|
+
- name: Setup MokoStandards tools
|
|
66
|
+
env:
|
|
67
|
+
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
|
68
|
+
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
|
69
|
+
run: |
|
|
70
|
+
git clone --depth 1 --branch version/04 --quiet \
|
|
71
|
+
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
|
72
|
+
/tmp/mokostandards
|
|
73
|
+
cd /tmp/mokostandards
|
|
74
|
+
composer install --no-dev --no-interaction --quiet
|
|
75
|
+
|
|
76
|
+
# -- STEP 1: Read version -----------------------------------------------
|
|
77
|
+
- name: "Step 1: Read version from README.md"
|
|
78
|
+
id: version
|
|
79
|
+
run: |
|
|
80
|
+
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
|
|
81
|
+
if [ -z "$VERSION" ]; then
|
|
82
|
+
echo "No VERSION in README.md — skipping release"
|
|
83
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
86
|
+
# Derive major.minor for branch naming (patches update existing branch)
|
|
87
|
+
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
|
|
88
|
+
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
|
|
89
|
+
|
|
90
|
+
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
|
91
|
+
MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}')
|
|
92
|
+
|
|
93
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
94
|
+
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
|
95
|
+
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
|
96
|
+
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
|
97
|
+
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
|
98
|
+
if [ "$PATCH" = "00" ]; then
|
|
99
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
100
|
+
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
|
101
|
+
echo "Version: $VERSION (patch 00 = development — skipping release)"
|
|
102
|
+
else
|
|
103
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
104
|
+
if [ "$PATCH" = "01" ]; then
|
|
105
|
+
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
|
106
|
+
echo "Version: $VERSION (first release — full pipeline)"
|
|
107
|
+
else
|
|
108
|
+
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
|
109
|
+
echo "Version: $VERSION (patch — platform version + badges only)"
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
- name: Check if already released
|
|
114
|
+
if: steps.version.outputs.skip != 'true'
|
|
115
|
+
id: check
|
|
116
|
+
run: |
|
|
117
|
+
TAG="${{ steps.version.outputs.release_tag }}"
|
|
118
|
+
BRANCH="${{ steps.version.outputs.branch }}"
|
|
119
|
+
|
|
120
|
+
TAG_EXISTS=false
|
|
121
|
+
BRANCH_EXISTS=false
|
|
122
|
+
|
|
123
|
+
git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true
|
|
124
|
+
git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH" && BRANCH_EXISTS=true
|
|
125
|
+
|
|
126
|
+
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
|
127
|
+
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
|
|
128
|
+
|
|
129
|
+
if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then
|
|
130
|
+
echo "already_released=true" >> "$GITHUB_OUTPUT"
|
|
131
|
+
else
|
|
132
|
+
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# -- SANITY CHECKS -------------------------------------------------------
|
|
136
|
+
- name: "Sanity: Pre-release validation"
|
|
137
|
+
if: >-
|
|
138
|
+
steps.version.outputs.skip != 'true' &&
|
|
139
|
+
steps.check.outputs.already_released != 'true'
|
|
140
|
+
run: |
|
|
141
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
142
|
+
ERRORS=0
|
|
143
|
+
|
|
144
|
+
echo "## Pre-Release Sanity Checks" >> $GITHUB_STEP_SUMMARY
|
|
145
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
146
|
+
|
|
147
|
+
# -- Version drift check (must pass before release) --------
|
|
148
|
+
README_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' README.md 2>/dev/null | head -1)
|
|
149
|
+
if [ "$README_VER" != "$VERSION" ]; then
|
|
150
|
+
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
151
|
+
ERRORS=$((ERRORS+1))
|
|
152
|
+
else
|
|
153
|
+
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Check CHANGELOG version matches
|
|
157
|
+
CL_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' CHANGELOG.md 2>/dev/null | head -1)
|
|
158
|
+
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
|
|
159
|
+
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
160
|
+
ERRORS=$((ERRORS+1))
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# Check composer.json version if present
|
|
164
|
+
if [ -f "composer.json" ]; then
|
|
165
|
+
COMP_VER=$(grep -oP '"version"\s*:\s*"\K[^"]+' composer.json 2>/dev/null | head -1)
|
|
166
|
+
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
|
|
167
|
+
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
168
|
+
ERRORS=$((ERRORS+1))
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# Common checks
|
|
173
|
+
if [ ! -f "LICENSE" ]; then
|
|
174
|
+
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
|
175
|
+
ERRORS=$((ERRORS+1))
|
|
176
|
+
else
|
|
177
|
+
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
|
|
181
|
+
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
|
|
182
|
+
else
|
|
183
|
+
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
187
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
188
|
+
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
|
189
|
+
else
|
|
190
|
+
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
|
|
194
|
+
# Always runs — every version change on main archives to version/XX.YY
|
|
195
|
+
- name: "Step 2: Version archive branch"
|
|
196
|
+
if: steps.check.outputs.already_released != 'true'
|
|
197
|
+
run: |
|
|
198
|
+
BRANCH="${{ steps.version.outputs.branch }}"
|
|
199
|
+
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
|
200
|
+
PATCH="${{ steps.version.outputs.version }}"
|
|
201
|
+
PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}')
|
|
202
|
+
|
|
203
|
+
# Check if branch exists
|
|
204
|
+
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
|
|
205
|
+
git push origin HEAD:"$BRANCH" --force
|
|
206
|
+
echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY
|
|
207
|
+
else
|
|
208
|
+
git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
|
|
209
|
+
git push origin "$BRANCH" --force
|
|
210
|
+
echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# -- STEP 3: Set platform version ----------------------------------------
|
|
214
|
+
- name: "Step 3: Set platform version"
|
|
215
|
+
if: >-
|
|
216
|
+
steps.version.outputs.skip != 'true' &&
|
|
217
|
+
steps.check.outputs.already_released != 'true'
|
|
218
|
+
run: |
|
|
219
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
220
|
+
php /tmp/mokostandards/api/cli/version_set_platform.php \
|
|
221
|
+
--path . --version "$VERSION" --branch main
|
|
222
|
+
|
|
223
|
+
# -- STEP 4: Update version badges ----------------------------------------
|
|
224
|
+
- name: "Step 4: Update version badges"
|
|
225
|
+
if: >-
|
|
226
|
+
steps.version.outputs.skip != 'true' &&
|
|
227
|
+
steps.check.outputs.already_released != 'true'
|
|
228
|
+
run: |
|
|
229
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
230
|
+
find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do
|
|
231
|
+
if grep -q '\[VERSION:' "$f" 2>/dev/null; then
|
|
232
|
+
sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f"
|
|
233
|
+
fi
|
|
234
|
+
done
|
|
235
|
+
|
|
236
|
+
# -- Commit all changes ---------------------------------------------------
|
|
237
|
+
- name: Commit release changes
|
|
238
|
+
if: >-
|
|
239
|
+
steps.version.outputs.skip != 'true' &&
|
|
240
|
+
steps.check.outputs.already_released != 'true'
|
|
241
|
+
run: |
|
|
242
|
+
if git diff --quiet && git diff --cached --quiet; then
|
|
243
|
+
echo "No changes to commit"
|
|
244
|
+
exit 0
|
|
245
|
+
fi
|
|
246
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
247
|
+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
248
|
+
git config --local user.name "github-actions[bot]"
|
|
249
|
+
git add -A
|
|
250
|
+
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
|
251
|
+
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
|
252
|
+
git push
|
|
253
|
+
|
|
254
|
+
# -- STEP 6: Create tag ---------------------------------------------------
|
|
255
|
+
- name: "Step 6: Create git tag"
|
|
256
|
+
if: >-
|
|
257
|
+
steps.version.outputs.skip != 'true' &&
|
|
258
|
+
steps.check.outputs.tag_exists != 'true' &&
|
|
259
|
+
steps.version.outputs.is_minor == 'true'
|
|
260
|
+
run: |
|
|
261
|
+
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
|
262
|
+
# Only create the major release tag if it doesn't exist yet
|
|
263
|
+
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
|
264
|
+
git tag "$RELEASE_TAG"
|
|
265
|
+
git push origin "$RELEASE_TAG"
|
|
266
|
+
echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
|
267
|
+
else
|
|
268
|
+
echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY
|
|
269
|
+
fi
|
|
270
|
+
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
|
271
|
+
|
|
272
|
+
# -- STEP 7: Create or update GitHub Release ------------------------------
|
|
273
|
+
- name: "Step 7: GitHub Release"
|
|
274
|
+
if: >-
|
|
275
|
+
steps.version.outputs.skip != 'true' &&
|
|
276
|
+
steps.check.outputs.tag_exists != 'true'
|
|
277
|
+
env:
|
|
278
|
+
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
|
279
|
+
run: |
|
|
280
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
281
|
+
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
|
282
|
+
BRANCH="${{ steps.version.outputs.branch }}"
|
|
283
|
+
MAJOR="${{ steps.version.outputs.major }}"
|
|
284
|
+
|
|
285
|
+
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
|
286
|
+
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
287
|
+
echo "$NOTES" > /tmp/release_notes.md
|
|
288
|
+
|
|
289
|
+
# Check if the major release already exists
|
|
290
|
+
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
|
291
|
+
|
|
292
|
+
if [ -z "$EXISTING" ]; then
|
|
293
|
+
# First release for this major: create GitHub Release
|
|
294
|
+
gh release create "$RELEASE_TAG" \
|
|
295
|
+
--title "v${MAJOR} (latest: ${VERSION})" \
|
|
296
|
+
--notes-file /tmp/release_notes.md \
|
|
297
|
+
--target "$BRANCH"
|
|
298
|
+
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
|
299
|
+
else
|
|
300
|
+
# Update existing major release with new version info
|
|
301
|
+
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
|
302
|
+
{
|
|
303
|
+
echo "$CURRENT_NOTES"
|
|
304
|
+
echo ""
|
|
305
|
+
echo "---"
|
|
306
|
+
echo "### ${VERSION}"
|
|
307
|
+
echo ""
|
|
308
|
+
cat /tmp/release_notes.md
|
|
309
|
+
} > /tmp/updated_notes.md
|
|
310
|
+
|
|
311
|
+
gh release edit "$RELEASE_TAG" \
|
|
312
|
+
--title "v${MAJOR} (latest: ${VERSION})" \
|
|
313
|
+
--notes-file /tmp/updated_notes.md
|
|
314
|
+
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
# -- Summary --------------------------------------------------------------
|
|
318
|
+
- name: Pipeline Summary
|
|
319
|
+
if: always()
|
|
320
|
+
run: |
|
|
321
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
322
|
+
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
|
323
|
+
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
|
324
|
+
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
|
325
|
+
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
|
326
|
+
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
|
327
|
+
else
|
|
328
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
329
|
+
echo "## Build & Release Complete" >> $GITHUB_STEP_SUMMARY
|
|
330
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
331
|
+
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
|
332
|
+
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
|
333
|
+
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
334
|
+
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
335
|
+
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
336
|
+
echo "| Release | [View](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
|
337
|
+
fi
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
# FILE INFORMATION
|
|
4
|
+
# DEFGROUP: Gitea.Workflow
|
|
5
|
+
# INGROUP: moko-platform.Automation
|
|
6
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
7
|
+
# PATH: /.gitea/workflows/branch-protection.yml
|
|
8
|
+
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
|
9
|
+
#
|
|
10
|
+
# +========================================================================+
|
|
11
|
+
# | BRANCH PROTECTION SETUP |
|
|
12
|
+
# +========================================================================+
|
|
13
|
+
# | |
|
|
14
|
+
# | Applies protection rules for: main, dev, rc, beta, alpha |
|
|
15
|
+
# | |
|
|
16
|
+
# | main — Require PR, block rejected reviews, no force push |
|
|
17
|
+
# | dev — Allow push, no force push, no delete |
|
|
18
|
+
# | rc — Allow push, no force push, no delete |
|
|
19
|
+
# | beta — Allow push, no force push, no delete |
|
|
20
|
+
# | alpha — Allow push, no force push, no delete |
|
|
21
|
+
# | |
|
|
22
|
+
# | jmiller has override authority on all branches. |
|
|
23
|
+
# | |
|
|
24
|
+
# +========================================================================+
|
|
25
|
+
|
|
26
|
+
name: Branch Protection Setup
|
|
27
|
+
|
|
28
|
+
on:
|
|
29
|
+
schedule:
|
|
30
|
+
- cron: '0 2 * * 1' # Weekly Monday 02:00 UTC
|
|
31
|
+
workflow_dispatch:
|
|
32
|
+
inputs:
|
|
33
|
+
dry_run:
|
|
34
|
+
description: 'Preview mode (no changes)'
|
|
35
|
+
required: false
|
|
36
|
+
type: boolean
|
|
37
|
+
default: false
|
|
38
|
+
repos:
|
|
39
|
+
description: 'Comma-separated repo names (empty = all governed repos)'
|
|
40
|
+
required: false
|
|
41
|
+
type: string
|
|
42
|
+
default: ''
|
|
43
|
+
|
|
44
|
+
env:
|
|
45
|
+
GITEA_URL: https://git.mokoconsulting.tech
|
|
46
|
+
GITEA_ORG: MokoConsulting
|
|
47
|
+
|
|
48
|
+
permissions:
|
|
49
|
+
contents: read
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
protect:
|
|
53
|
+
name: Apply Branch Protection Rules
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
|
|
56
|
+
steps:
|
|
57
|
+
- name: Determine target repos
|
|
58
|
+
id: repos
|
|
59
|
+
env:
|
|
60
|
+
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
|
61
|
+
run: |
|
|
62
|
+
API="${GITEA_URL}/api/v1"
|
|
63
|
+
|
|
64
|
+
# Platform/standards/infra repos to exclude
|
|
65
|
+
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting"
|
|
66
|
+
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
|
67
|
+
|
|
68
|
+
if [ -n "${{ inputs.repos }}" ]; then
|
|
69
|
+
# User-specified repos
|
|
70
|
+
REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ')
|
|
71
|
+
else
|
|
72
|
+
# Fetch all org repos
|
|
73
|
+
PAGE=1
|
|
74
|
+
REPOS=""
|
|
75
|
+
while true; do
|
|
76
|
+
BATCH=$(curl -sS \
|
|
77
|
+
-H "Authorization: token ${GA_TOKEN}" \
|
|
78
|
+
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \
|
|
79
|
+
| jq -r '.[].name // empty')
|
|
80
|
+
[ -z "$BATCH" ] && break
|
|
81
|
+
REPOS="$REPOS $BATCH"
|
|
82
|
+
PAGE=$((PAGE + 1))
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
# Filter out excluded repos
|
|
86
|
+
FILTERED=""
|
|
87
|
+
for REPO in $REPOS; do
|
|
88
|
+
SKIP=false
|
|
89
|
+
for EX in $EXCLUDE; do
|
|
90
|
+
if [ "$REPO" = "$EX" ]; then
|
|
91
|
+
SKIP=true
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
if [ "$SKIP" = "false" ]; then
|
|
96
|
+
FILTERED="$FILTERED $REPO"
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
REPOS="$FILTERED"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
echo "repos=$REPOS" >> "$GITHUB_OUTPUT"
|
|
103
|
+
COUNT=$(echo "$REPOS" | wc -w)
|
|
104
|
+
echo "📋 Target repos (${COUNT}): $REPOS"
|
|
105
|
+
|
|
106
|
+
- name: Apply protection rules
|
|
107
|
+
env:
|
|
108
|
+
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
|
109
|
+
DRY_RUN: ${{ inputs.dry_run || 'false' }}
|
|
110
|
+
run: |
|
|
111
|
+
API="${GITEA_URL}/api/v1"
|
|
112
|
+
REPOS="${{ steps.repos.outputs.repos }}"
|
|
113
|
+
|
|
114
|
+
SUCCESS=0
|
|
115
|
+
FAILED=0
|
|
116
|
+
SKIPPED=0
|
|
117
|
+
|
|
118
|
+
# ── Rule definitions ──────────────────────────────────────
|
|
119
|
+
# Only the CI bot (jmiller token) can push directly.
|
|
120
|
+
# All human contributors must use PRs.
|
|
121
|
+
# Force push disabled on all branches.
|
|
122
|
+
|
|
123
|
+
RULE_MAIN='{
|
|
124
|
+
"rule_name": "main",
|
|
125
|
+
"enable_push": true,
|
|
126
|
+
"enable_push_whitelist": true,
|
|
127
|
+
"push_whitelist_usernames": ["jmiller"],
|
|
128
|
+
"enable_force_push": false,
|
|
129
|
+
"enable_force_push_allowlist": false,
|
|
130
|
+
"force_push_allowlist_usernames": [],
|
|
131
|
+
"enable_merge_whitelist": false,
|
|
132
|
+
"required_approvals": 0,
|
|
133
|
+
"dismiss_stale_approvals": true,
|
|
134
|
+
"block_on_rejected_reviews": true,
|
|
135
|
+
"block_on_outdated_branch": false,
|
|
136
|
+
"priority": 1
|
|
137
|
+
}'
|
|
138
|
+
|
|
139
|
+
RULE_DEV='{
|
|
140
|
+
"rule_name": "dev",
|
|
141
|
+
"enable_push": true,
|
|
142
|
+
"enable_push_whitelist": true,
|
|
143
|
+
"push_whitelist_usernames": ["jmiller"],
|
|
144
|
+
"enable_force_push": false,
|
|
145
|
+
"enable_force_push_allowlist": false,
|
|
146
|
+
"force_push_allowlist_usernames": [],
|
|
147
|
+
"enable_merge_whitelist": false,
|
|
148
|
+
"required_approvals": 0,
|
|
149
|
+
"block_on_rejected_reviews": false,
|
|
150
|
+
"priority": 2
|
|
151
|
+
}'
|
|
152
|
+
|
|
153
|
+
RULE_RC='{
|
|
154
|
+
"rule_name": "rc",
|
|
155
|
+
"enable_push": true,
|
|
156
|
+
"enable_push_whitelist": true,
|
|
157
|
+
"push_whitelist_usernames": ["jmiller"],
|
|
158
|
+
"enable_force_push": false,
|
|
159
|
+
"enable_force_push_allowlist": false,
|
|
160
|
+
"force_push_allowlist_usernames": [],
|
|
161
|
+
"enable_merge_whitelist": false,
|
|
162
|
+
"required_approvals": 0,
|
|
163
|
+
"block_on_rejected_reviews": false,
|
|
164
|
+
"priority": 3
|
|
165
|
+
}'
|
|
166
|
+
|
|
167
|
+
RULE_BETA='{
|
|
168
|
+
"rule_name": "beta",
|
|
169
|
+
"enable_push": true,
|
|
170
|
+
"enable_push_whitelist": true,
|
|
171
|
+
"push_whitelist_usernames": ["jmiller"],
|
|
172
|
+
"enable_force_push": false,
|
|
173
|
+
"enable_force_push_allowlist": false,
|
|
174
|
+
"force_push_allowlist_usernames": [],
|
|
175
|
+
"enable_merge_whitelist": false,
|
|
176
|
+
"required_approvals": 0,
|
|
177
|
+
"block_on_rejected_reviews": false,
|
|
178
|
+
"priority": 4
|
|
179
|
+
}'
|
|
180
|
+
|
|
181
|
+
RULE_ALPHA='{
|
|
182
|
+
"rule_name": "alpha",
|
|
183
|
+
"enable_push": true,
|
|
184
|
+
"enable_push_whitelist": true,
|
|
185
|
+
"push_whitelist_usernames": ["jmiller"],
|
|
186
|
+
"enable_force_push": false,
|
|
187
|
+
"enable_force_push_allowlist": false,
|
|
188
|
+
"force_push_allowlist_usernames": [],
|
|
189
|
+
"enable_merge_whitelist": false,
|
|
190
|
+
"required_approvals": 0,
|
|
191
|
+
"block_on_rejected_reviews": false,
|
|
192
|
+
"priority": 5
|
|
193
|
+
}'
|
|
194
|
+
|
|
195
|
+
RULES=("$RULE_MAIN" "$RULE_DEV" "$RULE_RC" "$RULE_BETA" "$RULE_ALPHA")
|
|
196
|
+
RULE_NAMES=("main" "dev" "rc" "beta" "alpha")
|
|
197
|
+
|
|
198
|
+
# ── Apply rules to each repo ──────────────────────────────
|
|
199
|
+
for REPO in $REPOS; do
|
|
200
|
+
echo ""
|
|
201
|
+
echo "═══ ${REPO} ═══"
|
|
202
|
+
|
|
203
|
+
for i in "${!RULES[@]}"; do
|
|
204
|
+
RULE="${RULES[$i]}"
|
|
205
|
+
NAME="${RULE_NAMES[$i]}"
|
|
206
|
+
|
|
207
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
208
|
+
echo " [DRY RUN] Would apply rule: ${NAME}"
|
|
209
|
+
SKIPPED=$((SKIPPED + 1))
|
|
210
|
+
continue
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Delete existing rule if present (idempotent recreate)
|
|
214
|
+
ENCODED_NAME=$(echo "$NAME" | sed 's|/|%2F|g')
|
|
215
|
+
curl -sS -o /dev/null -w "" \
|
|
216
|
+
-X DELETE \
|
|
217
|
+
-H "Authorization: token ${GA_TOKEN}" \
|
|
218
|
+
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
|
|
219
|
+
|
|
220
|
+
# Create rule
|
|
221
|
+
RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
|
222
|
+
-X POST \
|
|
223
|
+
-H "Authorization: token ${GA_TOKEN}" \
|
|
224
|
+
-H "Content-Type: application/json" \
|
|
225
|
+
-d "$RULE" \
|
|
226
|
+
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections")
|
|
227
|
+
|
|
228
|
+
HTTP=$(echo "$RESPONSE" | tail -1)
|
|
229
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
230
|
+
|
|
231
|
+
if [ "$HTTP" = "201" ]; then
|
|
232
|
+
echo " ✅ ${NAME}"
|
|
233
|
+
SUCCESS=$((SUCCESS + 1))
|
|
234
|
+
else
|
|
235
|
+
echo " ❌ ${NAME} (HTTP ${HTTP}): $(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)"
|
|
236
|
+
FAILED=$((FAILED + 1))
|
|
237
|
+
fi
|
|
238
|
+
done
|
|
239
|
+
done
|
|
240
|
+
|
|
241
|
+
# ── Summary ───────────────────────────────────────────────
|
|
242
|
+
echo ""
|
|
243
|
+
echo "════════════════════════════════════════"
|
|
244
|
+
echo " ✅ Success: ${SUCCESS}"
|
|
245
|
+
echo " ❌ Failed: ${FAILED}"
|
|
246
|
+
echo " ⏭️ Skipped: ${SKIPPED}"
|
|
247
|
+
echo "════════════════════════════════════════"
|
|
248
|
+
|
|
249
|
+
if [ "$FAILED" -gt 0 ]; then
|
|
250
|
+
echo "::warning::${FAILED} rule(s) failed to apply"
|
|
251
|
+
fi
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
#
|
|
3
|
+
# This file is part of a Moko Consulting project.
|
|
4
|
+
#
|
|
5
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
6
|
+
#
|
|
7
|
+
# FILE INFORMATION
|
|
8
|
+
# DEFGROUP: GitHub.Workflow.Template
|
|
9
|
+
# INGROUP: MokoStandards.CI
|
|
10
|
+
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
|
11
|
+
# PATH: /templates/workflows/shared/changelog-validation.yml.template
|
|
12
|
+
# VERSION: 04.06.00
|
|
13
|
+
# BRIEF: Validates CHANGELOG.md format and version consistency
|
|
14
|
+
# NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos.
|
|
15
|
+
|
|
16
|
+
name: Changelog Validation
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
branches:
|
|
21
|
+
- main
|
|
22
|
+
pull_request:
|
|
23
|
+
branches:
|
|
24
|
+
- main
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
env:
|
|
31
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
validate-changelog:
|
|
35
|
+
name: Validate CHANGELOG.md
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- name: Checkout repository
|
|
40
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
41
|
+
|
|
42
|
+
- name: Check CHANGELOG.md exists
|
|
43
|
+
run: |
|
|
44
|
+
echo "### Changelog Validation" >> $GITHUB_STEP_SUMMARY
|
|
45
|
+
if [ ! -f "CHANGELOG.md" ]; then
|
|
46
|
+
echo "CHANGELOG.md not found in repository root." >> $GITHUB_STEP_SUMMARY
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
echo "CHANGELOG.md exists." >> $GITHUB_STEP_SUMMARY
|
|
50
|
+
|
|
51
|
+
- name: Check VERSION header matches README.md
|
|
52
|
+
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)
|
|
55
|
+
if [ -z "$README_VERSION" ]; then
|
|
56
|
+
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
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)
|
|
62
|
+
if [ -z "$CHANGELOG_VERSION" ]; then
|
|
63
|
+
echo "No version header found in CHANGELOG.md (expected \`## [XX.YY.ZZ] - YYYY-MM-DD\`)." >> $GITHUB_STEP_SUMMARY
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if [ "$CHANGELOG_VERSION" != "$README_VERSION" ]; then
|
|
68
|
+
echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." >> $GITHUB_STEP_SUMMARY
|
|
73
|
+
|
|
74
|
+
- name: Validate conventional changelog format
|
|
75
|
+
run: |
|
|
76
|
+
ERRORS=0
|
|
77
|
+
|
|
78
|
+
# Check that version entries follow ## [XX.YY.ZZ] - YYYY-MM-DD format
|
|
79
|
+
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
|
|
83
|
+
ERRORS=$((ERRORS + 1))
|
|
84
|
+
fi
|
|
85
|
+
done < <(grep -P '^\#\#\s*\[' CHANGELOG.md)
|
|
86
|
+
|
|
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
|
|
90
|
+
ERRORS=$((ERRORS + 1))
|
|
91
|
+
else
|
|
92
|
+
echo "Found ${ENTRY_COUNT} version entr(ies) in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
96
|
+
if [ "${ERRORS}" -gt 0 ]; then
|
|
97
|
+
echo "**${ERRORS} format issue(s) found.**" >> $GITHUB_STEP_SUMMARY
|
|
98
|
+
exit 1
|
|
99
|
+
else
|
|
100
|
+
echo "**Changelog format validation passed.**" >> $GITHUB_STEP_SUMMARY
|
|
101
|
+
fi
|