@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,198 @@
|
|
|
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.Cascade
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
|
9
|
+
# PATH: /.mokogit/workflows/cascade-dev.yml
|
|
10
|
+
# VERSION: 02.01.00
|
|
11
|
+
# BRIEF: Cascade main -> dev; auto-merge clean, auto-resolve VERSION-stamp-only conflicts, else notify
|
|
12
|
+
|
|
13
|
+
name: "Cascade Main -> Dev"
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
branches:
|
|
18
|
+
- main
|
|
19
|
+
# A sync-only push to main (workflow/CI-config files) should not immediately
|
|
20
|
+
# cascade to dev and light up the release pipeline. The daily schedule below
|
|
21
|
+
# still carries the .mokogit drift down to dev.
|
|
22
|
+
paths-ignore:
|
|
23
|
+
- '.mokogit/**'
|
|
24
|
+
- '.gitea/**'
|
|
25
|
+
- '.github/**'
|
|
26
|
+
# Daily safety net: catches drift even when main only received [skip ci] pushes
|
|
27
|
+
# (which never fire the push trigger above), and .mokogit-only syncs ignored above.
|
|
28
|
+
# Off-round minute to avoid a fleet-wide spike.
|
|
29
|
+
schedule:
|
|
30
|
+
- cron: '23 7 * * *'
|
|
31
|
+
workflow_dispatch:
|
|
32
|
+
|
|
33
|
+
permissions:
|
|
34
|
+
contents: write
|
|
35
|
+
pull-requests: write
|
|
36
|
+
|
|
37
|
+
env:
|
|
38
|
+
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
39
|
+
# ntfy destination is configured via repo or org variables (org vars are inherited).
|
|
40
|
+
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
|
41
|
+
NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'git-releases' }}
|
|
42
|
+
|
|
43
|
+
jobs:
|
|
44
|
+
cascade:
|
|
45
|
+
name: Cascade main -> dev
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- name: Checkout (full history for merge/resolve)
|
|
49
|
+
uses: actions/checkout@v4
|
|
50
|
+
with:
|
|
51
|
+
fetch-depth: 0
|
|
52
|
+
token: ${{ github.token }}
|
|
53
|
+
|
|
54
|
+
- name: Cascade main -> dev (auto-resolve version stamps, else notify)
|
|
55
|
+
env:
|
|
56
|
+
TOKEN: ${{ github.token }}
|
|
57
|
+
REPO: ${{ github.repository }}
|
|
58
|
+
run: |
|
|
59
|
+
set -uo pipefail
|
|
60
|
+
API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
|
|
61
|
+
AUTH="Authorization: token ${TOKEN}"
|
|
62
|
+
jqget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
|
63
|
+
|
|
64
|
+
# 0. dev must exist
|
|
65
|
+
if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then
|
|
66
|
+
echo "No dev branch - nothing to cascade."; exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# 1. is main ahead of dev?
|
|
70
|
+
AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
|
|
71
|
+
| python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
|
|
72
|
+
if [ "${AHEAD:-0}" -eq 0 ]; then
|
|
73
|
+
echo "dev already up to date with main."; exit 0
|
|
74
|
+
fi
|
|
75
|
+
echo "main is ${AHEAD} commit(s) ahead of dev."
|
|
76
|
+
|
|
77
|
+
# 2. reuse an open main->dev PR, else create one
|
|
78
|
+
PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \
|
|
79
|
+
| python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
|
|
80
|
+
if [ -z "$PR" ]; then
|
|
81
|
+
RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
|
|
82
|
+
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges when conflict-free, auto-resolves VERSION-stamp-only conflicts, otherwise left open for manual resolution."}')
|
|
83
|
+
PR=$(printf '%s' "$RESP" | jqget number)
|
|
84
|
+
if [ -z "$PR" ]; then
|
|
85
|
+
echo "::warning::Could not open cascade PR: $RESP"; exit 0
|
|
86
|
+
fi
|
|
87
|
+
echo "Opened cascade PR #${PR}"
|
|
88
|
+
else
|
|
89
|
+
echo "Reusing open cascade PR #${PR}"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
notify() {
|
|
93
|
+
curl -sS \
|
|
94
|
+
-H "Title: ${REPO}: dev cascade needs manual merge" \
|
|
95
|
+
-H "Tags: warning,twisted_rightwards_arrows" \
|
|
96
|
+
-H "Priority: high" \
|
|
97
|
+
-H "Click: ${MOKOGIT_URL}/${REPO}/pulls/${PR}" \
|
|
98
|
+
-d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \
|
|
99
|
+
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# 3. wait for MokoGIT to compute mergeability (conflict detection)
|
|
103
|
+
MERGEABLE=""
|
|
104
|
+
for _ in 1 2 3 4 5 6; do
|
|
105
|
+
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqget mergeable)
|
|
106
|
+
case "$MERGEABLE" in True|False) break ;; esac
|
|
107
|
+
sleep 3
|
|
108
|
+
done
|
|
109
|
+
echo "mergeable=${MERGEABLE}"
|
|
110
|
+
|
|
111
|
+
# 4a. conflict-free -> merge via API (existing behaviour)
|
|
112
|
+
if [ "$MERGEABLE" = "True" ]; then
|
|
113
|
+
CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
|
|
114
|
+
-X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
|
|
115
|
+
if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
|
|
116
|
+
echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
|
|
120
|
+
notify "could not be auto-merged (HTTP ${CODE})."
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# 4b. conflicts -> try to auto-resolve if they are ONLY VERSION-stamp lines.
|
|
125
|
+
echo "PR not cleanly mergeable; checking whether conflicts are VERSION-stamp-only..."
|
|
126
|
+
git config user.name "MokoGIT Cascade"
|
|
127
|
+
git config user.email "actions@mokoconsulting.tech"
|
|
128
|
+
git fetch --quiet origin main dev
|
|
129
|
+
git checkout -B dev origin/dev
|
|
130
|
+
|
|
131
|
+
if git merge --no-ff --no-commit origin/main >/dev/null 2>&1; then
|
|
132
|
+
# Became clean at git level (e.g. mergeability was still computing) -> commit + push.
|
|
133
|
+
git commit -m "chore(sync): cascade main -> dev [skip ci]" >/dev/null
|
|
134
|
+
git push origin dev
|
|
135
|
+
echo "Cascade merged cleanly at git level and pushed to dev."
|
|
136
|
+
exit 0
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
CONFLICTS=$(git diff --name-only --diff-filter=U)
|
|
140
|
+
echo "Conflicted files:"; echo "${CONFLICTS}"
|
|
141
|
+
|
|
142
|
+
# A conflict is "stamp-only" when every line inside every conflict block matches
|
|
143
|
+
# a version-stamp pattern (VERSION: header, <version> element, or CHANGELOG title).
|
|
144
|
+
is_stamp_only() {
|
|
145
|
+
awk '
|
|
146
|
+
/^<<<<<<< / { inc=1; next }
|
|
147
|
+
inc && /^=======$/ { next }
|
|
148
|
+
/^>>>>>>> / { inc=0; next }
|
|
149
|
+
inc { if ($0 !~ /(VERSION:|<version>|# Changelog)/) { bad=1 } }
|
|
150
|
+
END { exit(bad ? 1 : 0) }
|
|
151
|
+
' "$1"
|
|
152
|
+
}
|
|
153
|
+
# Resolve a stamp-only file by keeping dev (ours) for the conflicting lines,
|
|
154
|
+
# preserving all auto-merged content around them.
|
|
155
|
+
keep_ours() {
|
|
156
|
+
awk '
|
|
157
|
+
/^<<<<<<< / { inc=1; side="ours"; next }
|
|
158
|
+
inc && /^=======$/ { side="theirs"; next }
|
|
159
|
+
/^>>>>>>> / { inc=0; next }
|
|
160
|
+
{ if (!inc) { print; next } if (side=="ours") print }
|
|
161
|
+
' "$1" > "$1.resolved" && mv "$1.resolved" "$1"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
ALL_STAMP=1
|
|
165
|
+
for f in ${CONFLICTS}; do
|
|
166
|
+
if ! is_stamp_only "$f"; then
|
|
167
|
+
echo "::notice::$f has non-stamp conflicts -> manual resolution required."
|
|
168
|
+
ALL_STAMP=0; break
|
|
169
|
+
fi
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
if [ "$ALL_STAMP" != "1" ]; then
|
|
173
|
+
git merge --abort || true
|
|
174
|
+
notify "has non-version-stamp conflicts and cannot be auto-resolved."
|
|
175
|
+
exit 0
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
echo "All conflicts are VERSION-stamp-only; resolving in favour of dev."
|
|
179
|
+
for f in ${CONFLICTS}; do
|
|
180
|
+
keep_ours "$f"
|
|
181
|
+
git add "$f"
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
# Best-effort: normalise stamps to dev's version if mokocli is available.
|
|
185
|
+
if [ -f /opt/mokocli/cli/version_check.php ]; then
|
|
186
|
+
php /opt/mokocli/cli/version_check.php --fix || true
|
|
187
|
+
git add -A
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
git commit -m "chore(sync): cascade main -> dev (auto-resolved version stamps) [skip ci]" >/dev/null
|
|
191
|
+
git push origin dev
|
|
192
|
+
echo "Cascade auto-resolved and pushed to dev."
|
|
193
|
+
|
|
194
|
+
# Close the now-redundant PR (its changes are in dev) with an explanatory comment.
|
|
195
|
+
curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
|
|
196
|
+
-d '{"body":"Auto-resolved VERSION-stamp-only conflicts and pushed the merge to dev. Closing."}' >/dev/null || true
|
|
197
|
+
curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
|
|
198
|
+
-d '{"state":"closed"}' >/dev/null || true
|
|
@@ -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 .mokogit/workflows/changelog-validation.yml in governed repos.
|
|
15
|
+
|
|
16
|
+
name: "Universal: 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
|
|
@@ -0,0 +1,203 @@
|
|
|
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.CI
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
|
9
|
+
# PATH: /.mokogit/workflows/ci-generic.yml
|
|
10
|
+
# VERSION: 01.00.00
|
|
11
|
+
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
|
|
12
|
+
|
|
13
|
+
name: "Generic: Project CI"
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
pull_request:
|
|
17
|
+
branches:
|
|
18
|
+
- main
|
|
19
|
+
- dev
|
|
20
|
+
- dev/**
|
|
21
|
+
- rc/**
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
|
|
27
|
+
env:
|
|
28
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
# ── Lint & Validate ───────────────────────────────────────────────────
|
|
32
|
+
lint:
|
|
33
|
+
name: Lint & Validate
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
# Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
|
|
36
|
+
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- name: Checkout
|
|
40
|
+
uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- name: Detect toolchain
|
|
43
|
+
id: detect
|
|
44
|
+
run: |
|
|
45
|
+
HAS_PHP=false
|
|
46
|
+
HAS_NODE=false
|
|
47
|
+
[ -f "composer.json" ] && HAS_PHP=true
|
|
48
|
+
[ -f "package.json" ] && HAS_NODE=true
|
|
49
|
+
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
|
50
|
+
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
|
51
|
+
echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
|
|
52
|
+
|
|
53
|
+
- name: Setup PHP
|
|
54
|
+
if: steps.detect.outputs.has_php == 'true'
|
|
55
|
+
run: |
|
|
56
|
+
if ! command -v php &> /dev/null; then
|
|
57
|
+
sudo apt-get update -qq
|
|
58
|
+
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
|
59
|
+
fi
|
|
60
|
+
php -v
|
|
61
|
+
|
|
62
|
+
- name: Setup Node.js
|
|
63
|
+
if: steps.detect.outputs.has_node == 'true'
|
|
64
|
+
uses: actions/setup-node@v4
|
|
65
|
+
with:
|
|
66
|
+
node-version: '20'
|
|
67
|
+
|
|
68
|
+
- name: Install PHP dependencies
|
|
69
|
+
if: steps.detect.outputs.has_php == 'true'
|
|
70
|
+
run: |
|
|
71
|
+
if [ -f "composer.json" ]; then
|
|
72
|
+
composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
- name: Install Node.js dependencies
|
|
76
|
+
if: steps.detect.outputs.has_node == 'true'
|
|
77
|
+
run: |
|
|
78
|
+
if [ -f "package.json" ]; then
|
|
79
|
+
npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
- name: PHP syntax check
|
|
83
|
+
if: steps.detect.outputs.has_php == 'true'
|
|
84
|
+
run: |
|
|
85
|
+
ERRORS=0
|
|
86
|
+
while IFS= read -r -d '' file; do
|
|
87
|
+
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
|
88
|
+
echo "::error file=${file}::PHP syntax error"
|
|
89
|
+
ERRORS=$((ERRORS + 1))
|
|
90
|
+
fi
|
|
91
|
+
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
|
|
92
|
+
|
|
93
|
+
echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
|
|
94
|
+
if [ "$ERRORS" -eq 0 ]; then
|
|
95
|
+
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
|
|
96
|
+
else
|
|
97
|
+
echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
- name: TypeScript/JavaScript lint
|
|
102
|
+
if: steps.detect.outputs.has_node == 'true'
|
|
103
|
+
run: |
|
|
104
|
+
if [ -f "node_modules/.bin/eslint" ]; then
|
|
105
|
+
npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
|
|
106
|
+
echo "## ESLint" >> $GITHUB_STEP_SUMMARY
|
|
107
|
+
echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
|
|
108
|
+
elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
|
|
109
|
+
echo "::warning::ESLint config found but eslint not installed"
|
|
110
|
+
else
|
|
111
|
+
echo "No ESLint configured — skipping"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
- name: TypeScript compile check
|
|
115
|
+
if: steps.detect.outputs.has_node == 'true'
|
|
116
|
+
run: |
|
|
117
|
+
if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
|
|
118
|
+
npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
|
|
119
|
+
echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
|
|
120
|
+
echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
- name: PHPStan static analysis
|
|
124
|
+
if: steps.detect.outputs.has_php == 'true'
|
|
125
|
+
run: |
|
|
126
|
+
if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
|
|
127
|
+
vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# ── Tests ─────────────────────────────────────────────────────────────
|
|
131
|
+
test:
|
|
132
|
+
name: Tests
|
|
133
|
+
runs-on: ubuntu-latest
|
|
134
|
+
# Independent job (no `needs: lint`): the MokoGIT Actions scheduler does not
|
|
135
|
+
# offer the dependent 2nd job of a needs-chain to runners, so it stalls in
|
|
136
|
+
# "waiting" and is reaped by ABANDONED_JOB_TIMEOUT. Guard template repos
|
|
137
|
+
# directly (same condition lint uses) instead of gating on lint's result.
|
|
138
|
+
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
|
139
|
+
|
|
140
|
+
steps:
|
|
141
|
+
- name: Checkout
|
|
142
|
+
uses: actions/checkout@v4
|
|
143
|
+
|
|
144
|
+
- name: Detect toolchain
|
|
145
|
+
id: detect
|
|
146
|
+
run: |
|
|
147
|
+
HAS_PHP=false
|
|
148
|
+
HAS_NODE=false
|
|
149
|
+
[ -f "composer.json" ] && HAS_PHP=true
|
|
150
|
+
[ -f "package.json" ] && HAS_NODE=true
|
|
151
|
+
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
|
152
|
+
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
|
153
|
+
|
|
154
|
+
- name: Setup PHP
|
|
155
|
+
if: steps.detect.outputs.has_php == 'true'
|
|
156
|
+
run: |
|
|
157
|
+
if ! command -v php &> /dev/null; then
|
|
158
|
+
sudo apt-get update -qq
|
|
159
|
+
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
- name: Setup Node.js
|
|
163
|
+
if: steps.detect.outputs.has_node == 'true'
|
|
164
|
+
uses: actions/setup-node@v4
|
|
165
|
+
with:
|
|
166
|
+
node-version: '20'
|
|
167
|
+
|
|
168
|
+
- name: Install dependencies
|
|
169
|
+
run: |
|
|
170
|
+
[ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
|
171
|
+
[ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; } || true
|
|
172
|
+
|
|
173
|
+
- name: Run PHP tests
|
|
174
|
+
if: steps.detect.outputs.has_php == 'true'
|
|
175
|
+
run: |
|
|
176
|
+
if [ -f "vendor/bin/phpunit" ]; then
|
|
177
|
+
vendor/bin/phpunit --testdox 2>&1
|
|
178
|
+
echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
|
|
179
|
+
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
|
180
|
+
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
|
|
181
|
+
echo "::warning::PHPUnit config found but phpunit not installed"
|
|
182
|
+
else
|
|
183
|
+
echo "No PHPUnit configured — skipping"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
- name: Run Node.js tests
|
|
187
|
+
if: steps.detect.outputs.has_node == 'true'
|
|
188
|
+
run: |
|
|
189
|
+
if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
|
|
190
|
+
npm test 2>&1
|
|
191
|
+
echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
|
|
192
|
+
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
|
193
|
+
else
|
|
194
|
+
echo "No test script in package.json — skipping"
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
- name: Build check
|
|
198
|
+
run: |
|
|
199
|
+
if [ -f "Makefile" ]; then
|
|
200
|
+
make build 2>&1 || echo "::warning::Build failed or not configured"
|
|
201
|
+
elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
|
|
202
|
+
npm run build 2>&1 || echo "::warning::Build failed"
|
|
203
|
+
fi
|
|
@@ -0,0 +1,75 @@
|
|
|
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/ci-issue-reporter.yml
|
|
10
|
+
# VERSION: 01.00.00
|
|
11
|
+
# BRIEF: Reusable workflow � creates/updates a MokoGIT issue when a CI gate fails.
|
|
12
|
+
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
|
13
|
+
|
|
14
|
+
name: "Universal: CI Issue Reporter"
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
workflow_call:
|
|
18
|
+
inputs:
|
|
19
|
+
gate:
|
|
20
|
+
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
|
21
|
+
required: true
|
|
22
|
+
type: string
|
|
23
|
+
details:
|
|
24
|
+
description: "Human-readable failure description"
|
|
25
|
+
required: true
|
|
26
|
+
type: string
|
|
27
|
+
severity:
|
|
28
|
+
description: "error or warning"
|
|
29
|
+
required: false
|
|
30
|
+
type: string
|
|
31
|
+
default: "error"
|
|
32
|
+
workflow:
|
|
33
|
+
description: "Workflow name for the issue title"
|
|
34
|
+
required: false
|
|
35
|
+
type: string
|
|
36
|
+
default: ""
|
|
37
|
+
secrets:
|
|
38
|
+
MOKOGIT_TOKEN:
|
|
39
|
+
required: true
|
|
40
|
+
|
|
41
|
+
env:
|
|
42
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
43
|
+
|
|
44
|
+
jobs:
|
|
45
|
+
report:
|
|
46
|
+
name: "Report: ${{ inputs.gate }}"
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
|
|
49
|
+
steps:
|
|
50
|
+
- name: Clone MokoCLI
|
|
51
|
+
env:
|
|
52
|
+
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
53
|
+
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
54
|
+
run: |
|
|
55
|
+
git clone --depth 1 --filter=blob:none --sparse "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
|
56
|
+
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
|
57
|
+
|
|
58
|
+
- name: Report CI failure
|
|
59
|
+
env:
|
|
60
|
+
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
61
|
+
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
62
|
+
# Route all workflow_call inputs through env vars — referencing ${{ inputs.* }}
|
|
63
|
+
# directly in a run: block interpolates at YAML-parse time and is a shell-injection
|
|
64
|
+
# vector. Shell variables are not re-parsed, so a crafted input can't break out.
|
|
65
|
+
INPUT_GATE: ${{ inputs.gate }}
|
|
66
|
+
INPUT_DETAILS: ${{ inputs.details }}
|
|
67
|
+
INPUT_SEVERITY: ${{ inputs.severity }}
|
|
68
|
+
INPUT_WORKFLOW: ${{ inputs.workflow }}
|
|
69
|
+
run: |
|
|
70
|
+
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
|
71
|
+
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
|
72
|
+
--gate "$INPUT_GATE" \
|
|
73
|
+
--details "$INPUT_DETAILS" \
|
|
74
|
+
--severity "$INPUT_SEVERITY" \
|
|
75
|
+
--workflow "$INPUT_WORKFLOW"
|
|
@@ -0,0 +1,87 @@
|
|
|
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.Maintenance
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
|
9
|
+
# PATH: /.mokogit/workflows/cleanup.yml
|
|
10
|
+
# VERSION: 01.00.00
|
|
11
|
+
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
|
12
|
+
|
|
13
|
+
name: "Universal: Repository Cleanup"
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
schedule:
|
|
17
|
+
- cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: write
|
|
22
|
+
|
|
23
|
+
env:
|
|
24
|
+
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
cleanup:
|
|
28
|
+
name: Clean Merged Branches
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
with:
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
token: ${{ secrets.MOKOGIT_TOKEN }}
|
|
37
|
+
|
|
38
|
+
- name: Delete merged branches
|
|
39
|
+
env:
|
|
40
|
+
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
41
|
+
run: |
|
|
42
|
+
echo "=== Merged Branch Cleanup ==="
|
|
43
|
+
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
|
|
44
|
+
|
|
45
|
+
# List branches via API
|
|
46
|
+
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
|
47
|
+
"${API}/branches?limit=50" | jq -r '.[].name')
|
|
48
|
+
|
|
49
|
+
DELETED=0
|
|
50
|
+
for BRANCH in $BRANCHES; do
|
|
51
|
+
# Skip protected branches
|
|
52
|
+
case "$BRANCH" in
|
|
53
|
+
main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
|
|
54
|
+
esac
|
|
55
|
+
|
|
56
|
+
# Check if branch is merged into main
|
|
57
|
+
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
|
58
|
+
echo " Deleting merged branch: ${BRANCH}"
|
|
59
|
+
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
|
60
|
+
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
|
61
|
+
DELETED=$((DELETED + 1))
|
|
62
|
+
fi
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
echo "Deleted ${DELETED} merged branch(es)"
|
|
66
|
+
|
|
67
|
+
- name: Clean old workflow runs
|
|
68
|
+
env:
|
|
69
|
+
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
70
|
+
run: |
|
|
71
|
+
echo "=== Workflow Run Cleanup ==="
|
|
72
|
+
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
|
|
73
|
+
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
|
74
|
+
|
|
75
|
+
# Get old completed runs
|
|
76
|
+
RUNS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
|
77
|
+
"${API}/actions/runs?status=completed&limit=50" | \
|
|
78
|
+
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
|
79
|
+
|
|
80
|
+
DELETED=0
|
|
81
|
+
for RUN_ID in $RUNS; do
|
|
82
|
+
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
|
83
|
+
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
|
84
|
+
DELETED=$((DELETED + 1))
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
echo "Deleted ${DELETED} old workflow run(s)"
|