@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.
Files changed (86) hide show
  1. package/.gitattributes +94 -0
  2. package/.gitmessage +9 -0
  3. package/.mokogitea/ISSUE_TEMPLATE/adr.md +110 -0
  4. package/.mokogitea/ISSUE_TEMPLATE/bug_report.md +48 -0
  5. package/.mokogitea/ISSUE_TEMPLATE/config.yml +18 -0
  6. package/.mokogitea/ISSUE_TEMPLATE/documentation.md +52 -0
  7. package/.mokogitea/ISSUE_TEMPLATE/enterprise_support.md +85 -0
  8. package/.mokogitea/ISSUE_TEMPLATE/feature_request.md +51 -0
  9. package/.mokogitea/ISSUE_TEMPLATE/firewall-request.md +190 -0
  10. package/.mokogitea/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
  11. package/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +67 -0
  12. package/.mokogitea/ISSUE_TEMPLATE/mcp_tool_request.md +49 -0
  13. package/.mokogitea/ISSUE_TEMPLATE/question.md +82 -0
  14. package/.mokogitea/ISSUE_TEMPLATE/rfc.md +126 -0
  15. package/.mokogitea/ISSUE_TEMPLATE/security.md +51 -0
  16. package/.mokogitea/ISSUE_TEMPLATE/version.md +24 -0
  17. package/.mokogitea/auto-assign.yml +76 -0
  18. package/.mokogitea/auto-dev-issue.yml +207 -0
  19. package/.mokogitea/auto-release.yml +337 -0
  20. package/.mokogitea/branch-protection.yml +251 -0
  21. package/.mokogitea/changelog-validation.yml +101 -0
  22. package/.mokogitea/codeql-analysis.yml +115 -0
  23. package/.mokogitea/copilot-agent.yml +44 -0
  24. package/.mokogitea/deploy-demo.yml +734 -0
  25. package/.mokogitea/deploy-dev.yml +700 -0
  26. package/.mokogitea/enterprise-firewall-setup.yml +758 -0
  27. package/.mokogitea/manifest.xml +25 -0
  28. package/.mokogitea/mcp-auto-release.yml +278 -0
  29. package/.mokogitea/mcp-build-test.yml +65 -0
  30. package/.mokogitea/mcp-sdk-check.yml +109 -0
  31. package/.mokogitea/mcp-tool-inventory.yml +61 -0
  32. package/.mokogitea/pr-branch-check.yml +90 -0
  33. package/.mokogitea/repository-cleanup.yml +525 -0
  34. package/.mokogitea/standards-compliance.yml +2614 -0
  35. package/.mokogitea/sync-version-on-merge.yml +133 -0
  36. package/.mokogitea/workflows/auto-assign.yml +76 -0
  37. package/.mokogitea/workflows/auto-bump.yml +66 -0
  38. package/.mokogitea/workflows/auto-dev-issue.yml +207 -0
  39. package/.mokogitea/workflows/auto-release.yml +341 -0
  40. package/.mokogitea/workflows/branch-cleanup.yml +48 -0
  41. package/.mokogitea/workflows/cascade-dev.yml +10 -0
  42. package/.mokogitea/workflows/changelog-validation.yml +101 -0
  43. package/.mokogitea/workflows/ci-generic.yml +204 -0
  44. package/.mokogitea/workflows/cleanup.yml +87 -0
  45. package/.mokogitea/workflows/codeql-analysis.yml +115 -0
  46. package/.mokogitea/workflows/copilot-agent.yml +44 -0
  47. package/.mokogitea/workflows/deploy-manual.yml +126 -0
  48. package/.mokogitea/workflows/enterprise-firewall-setup.yml +758 -0
  49. package/.mokogitea/workflows/gitleaks.yml +96 -0
  50. package/.mokogitea/workflows/issue-branch.yml +73 -0
  51. package/.mokogitea/workflows/mcp-auto-release.yml +280 -0
  52. package/.mokogitea/workflows/mcp-build-test.yml +65 -0
  53. package/.mokogitea/workflows/mcp-sdk-check.yml +109 -0
  54. package/.mokogitea/workflows/mcp-tool-inventory.yml +61 -0
  55. package/.mokogitea/workflows/notify.yml +70 -0
  56. package/.mokogitea/workflows/npm-publish.yml +51 -0
  57. package/.mokogitea/workflows/pr-check.yml +508 -0
  58. package/.mokogitea/workflows/pre-release.yml +11 -0
  59. package/.mokogitea/workflows/repo-health.yml +711 -0
  60. package/.mokogitea/workflows/repository-cleanup.yml +525 -0
  61. package/.mokogitea/workflows/security-audit.yml +82 -0
  62. package/.mokogitea/workflows/standards-compliance.yml +2614 -0
  63. package/.mokogitea/workflows/sync-version-on-merge.yml +130 -0
  64. package/.mokogitea/workflows/update-server.yml +312 -0
  65. package/CHANGELOG.md +145 -0
  66. package/CLAUDE.md +43 -0
  67. package/CONTRIBUTING.md +161 -0
  68. package/README.md +286 -0
  69. package/SECURITY.md +91 -0
  70. package/automation/ci-issue-reporter.sh +237 -0
  71. package/config.example.json +13 -0
  72. package/dist/client.d.ts +15 -0
  73. package/dist/client.js +104 -0
  74. package/dist/config.d.ts +4 -0
  75. package/dist/config.js +48 -0
  76. package/dist/index.d.ts +3 -0
  77. package/dist/index.js +1119 -0
  78. package/dist/types.d.ts +20 -0
  79. package/dist/types.js +16 -0
  80. package/package.json +34 -0
  81. package/scripts/setup.mjs +40 -0
  82. package/src/client.ts +120 -0
  83. package/src/config.ts +58 -0
  84. package/src/index.ts +1712 -0
  85. package/src/types.ts +37 -0
  86. 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