@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,70 @@
|
|
|
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: Gitea.Workflow
|
|
7
|
+
# INGROUP: MokoStandards.Notifications
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
|
9
|
+
# PATH: /.gitea/workflows/notify.yml
|
|
10
|
+
# VERSION: 01.00.00
|
|
11
|
+
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
|
12
|
+
|
|
13
|
+
name: "Universal: Notifications"
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
workflow_run:
|
|
17
|
+
workflows:
|
|
18
|
+
- "Joomla Build & Release"
|
|
19
|
+
- "Joomla Extension CI"
|
|
20
|
+
- "Deploy"
|
|
21
|
+
types:
|
|
22
|
+
- completed
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
|
|
27
|
+
env:
|
|
28
|
+
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
|
29
|
+
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }}
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
notify:
|
|
33
|
+
name: Send Notification
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
if: >-
|
|
36
|
+
github.event.workflow_run.conclusion == 'success' ||
|
|
37
|
+
github.event.workflow_run.conclusion == 'failure'
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- name: Notify on success (releases only)
|
|
41
|
+
if: >-
|
|
42
|
+
github.event.workflow_run.conclusion == 'success' &&
|
|
43
|
+
contains(github.event.workflow_run.name, 'Release')
|
|
44
|
+
run: |
|
|
45
|
+
REPO="${{ github.event.repository.name }}"
|
|
46
|
+
WORKFLOW="${{ github.event.workflow_run.name }}"
|
|
47
|
+
URL="${{ github.event.workflow_run.html_url }}"
|
|
48
|
+
|
|
49
|
+
curl -sS \
|
|
50
|
+
-H "Title: ${REPO} released" \
|
|
51
|
+
-H "Tags: white_check_mark,package" \
|
|
52
|
+
-H "Priority: default" \
|
|
53
|
+
-H "Click: ${URL}" \
|
|
54
|
+
-d "${WORKFLOW} completed successfully." \
|
|
55
|
+
"${NTFY_URL}/${NTFY_TOPIC}"
|
|
56
|
+
|
|
57
|
+
- name: Notify on failure
|
|
58
|
+
if: github.event.workflow_run.conclusion == 'failure'
|
|
59
|
+
run: |
|
|
60
|
+
REPO="${{ github.event.repository.name }}"
|
|
61
|
+
WORKFLOW="${{ github.event.workflow_run.name }}"
|
|
62
|
+
URL="${{ github.event.workflow_run.html_url }}"
|
|
63
|
+
|
|
64
|
+
curl -sS \
|
|
65
|
+
-H "Title: ${REPO} workflow failed" \
|
|
66
|
+
-H "Tags: x,warning" \
|
|
67
|
+
-H "Priority: high" \
|
|
68
|
+
-H "Click: ${URL}" \
|
|
69
|
+
-d "${WORKFLOW} failed. Check the run for details." \
|
|
70
|
+
"${NTFY_URL}/${NTFY_TOPIC}"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
name: "Publish to npm"
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: '20'
|
|
23
|
+
registry-url: 'https://registry.npmjs.org'
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm install
|
|
27
|
+
|
|
28
|
+
- name: Build
|
|
29
|
+
run: npm run build
|
|
30
|
+
|
|
31
|
+
- name: Check if version changed
|
|
32
|
+
id: version
|
|
33
|
+
run: |
|
|
34
|
+
PKG_NAME=$(node -p "require('./package.json').name")
|
|
35
|
+
PKG_VERSION=$(node -p "require('./package.json').version")
|
|
36
|
+
PUBLISHED=$(npm view "${PKG_NAME}@${PKG_VERSION}" version 2>/dev/null || echo "")
|
|
37
|
+
if [ "$PUBLISHED" = "$PKG_VERSION" ]; then
|
|
38
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
39
|
+
echo "Version ${PKG_VERSION} already published, skipping."
|
|
40
|
+
else
|
|
41
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
42
|
+
echo "Publishing ${PKG_NAME}@${PKG_VERSION}"
|
|
43
|
+
fi
|
|
44
|
+
env:
|
|
45
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
46
|
+
|
|
47
|
+
- name: Publish
|
|
48
|
+
if: steps.version.outputs.skip != 'true'
|
|
49
|
+
run: npm publish --access public
|
|
50
|
+
env:
|
|
51
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,508 @@
|
|
|
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: Gitea.Workflow
|
|
7
|
+
# INGROUP: moko-platform.CI
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
|
9
|
+
# PATH: /templates/workflows/universal/pr-check.yml.template
|
|
10
|
+
# VERSION: 09.23.00
|
|
11
|
+
# BRIEF: PR gate — branch policy + code validation before merge
|
|
12
|
+
|
|
13
|
+
name: "Universal: PR Check"
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
pull_request:
|
|
17
|
+
types: [opened, synchronize, reopened, edited]
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
pull-requests: write
|
|
22
|
+
|
|
23
|
+
env:
|
|
24
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
# ── Branch Policy ──────────────────────────────────────────────────────
|
|
28
|
+
branch-policy:
|
|
29
|
+
name: Branch Policy
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- name: Check branch merge target
|
|
33
|
+
run: |
|
|
34
|
+
HEAD="${{ github.head_ref }}"
|
|
35
|
+
BASE="${{ github.base_ref }}"
|
|
36
|
+
|
|
37
|
+
echo "PR: ${HEAD} → ${BASE}"
|
|
38
|
+
|
|
39
|
+
ALLOWED=true
|
|
40
|
+
REASON=""
|
|
41
|
+
|
|
42
|
+
case "$HEAD" in
|
|
43
|
+
feature/*|feat/*)
|
|
44
|
+
if [ "$BASE" != "dev" ]; then
|
|
45
|
+
ALLOWED=false
|
|
46
|
+
REASON="Feature branches must target 'dev', not '${BASE}'"
|
|
47
|
+
fi
|
|
48
|
+
;;
|
|
49
|
+
fix/*|bugfix/*)
|
|
50
|
+
if [ "$BASE" != "dev" ]; then
|
|
51
|
+
ALLOWED=false
|
|
52
|
+
REASON="Fix branches must target 'dev', not '${BASE}'"
|
|
53
|
+
fi
|
|
54
|
+
;;
|
|
55
|
+
patch/*)
|
|
56
|
+
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
|
|
57
|
+
ALLOWED=false
|
|
58
|
+
REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
|
|
59
|
+
fi
|
|
60
|
+
;;
|
|
61
|
+
hotfix/*)
|
|
62
|
+
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
|
63
|
+
ALLOWED=false
|
|
64
|
+
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
|
65
|
+
fi
|
|
66
|
+
;;
|
|
67
|
+
rc)
|
|
68
|
+
if [ "$BASE" != "main" ]; then
|
|
69
|
+
ALLOWED=false
|
|
70
|
+
REASON="RC branch can only merge into 'main', not '${BASE}'"
|
|
71
|
+
fi
|
|
72
|
+
;;
|
|
73
|
+
dev)
|
|
74
|
+
if [ "$BASE" != "main" ]; then
|
|
75
|
+
ALLOWED=false
|
|
76
|
+
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
|
77
|
+
fi
|
|
78
|
+
;;
|
|
79
|
+
esac
|
|
80
|
+
|
|
81
|
+
if [ "$ALLOWED" = false ]; then
|
|
82
|
+
echo "::error::${REASON}"
|
|
83
|
+
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
|
84
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
85
|
+
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
|
86
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
87
|
+
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
|
88
|
+
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
|
89
|
+
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
|
90
|
+
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
91
|
+
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
92
|
+
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
|
97
|
+
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
|
98
|
+
|
|
99
|
+
# ── Code Validation ────────────────────────────────────────────────────
|
|
100
|
+
validate:
|
|
101
|
+
name: Validate PR
|
|
102
|
+
runs-on: ubuntu-latest
|
|
103
|
+
|
|
104
|
+
steps:
|
|
105
|
+
- name: Checkout
|
|
106
|
+
uses: actions/checkout@v4
|
|
107
|
+
|
|
108
|
+
- name: Check for merge conflict markers
|
|
109
|
+
run: |
|
|
110
|
+
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
|
111
|
+
if [ -n "$CONFLICTS" ]; then
|
|
112
|
+
echo "::error::Merge conflict markers found in source files"
|
|
113
|
+
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
|
|
114
|
+
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
115
|
+
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
|
116
|
+
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
echo "No conflict markers found"
|
|
120
|
+
|
|
121
|
+
- name: Detect platform
|
|
122
|
+
id: platform
|
|
123
|
+
run: |
|
|
124
|
+
# Read platform from XML manifest (<platform> tag) or plain text fallback
|
|
125
|
+
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
|
|
126
|
+
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
|
|
127
|
+
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
|
128
|
+
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
|
129
|
+
|
|
130
|
+
- name: Setup PHP
|
|
131
|
+
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
|
132
|
+
run: |
|
|
133
|
+
if ! command -v php &> /dev/null; then
|
|
134
|
+
sudo apt-get update -qq
|
|
135
|
+
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
- name: PHP syntax check
|
|
139
|
+
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
|
140
|
+
run: |
|
|
141
|
+
ERRORS=0
|
|
142
|
+
while IFS= read -r -d '' file; do
|
|
143
|
+
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
|
144
|
+
ERRORS=$((ERRORS + 1))
|
|
145
|
+
fi
|
|
146
|
+
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0)
|
|
147
|
+
echo "PHP lint: ${ERRORS} error(s)"
|
|
148
|
+
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
|
|
149
|
+
|
|
150
|
+
- name: Joomla JEXEC guard check
|
|
151
|
+
if: steps.platform.outputs.platform == 'joomla'
|
|
152
|
+
run: |
|
|
153
|
+
ERRORS=0
|
|
154
|
+
while IFS= read -r -d '' file; do
|
|
155
|
+
# Skip vendor, node_modules, and index.html stub files
|
|
156
|
+
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
|
|
157
|
+
# Check first 10 lines for JEXEC or JPATH guard
|
|
158
|
+
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
|
|
159
|
+
echo "::error file=${file}::Missing JEXEC guard: ${file}"
|
|
160
|
+
ERRORS=$((ERRORS + 1))
|
|
161
|
+
fi
|
|
162
|
+
done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0)
|
|
163
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
164
|
+
echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard"
|
|
165
|
+
echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY
|
|
166
|
+
echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY
|
|
167
|
+
exit 1
|
|
168
|
+
fi
|
|
169
|
+
echo "JEXEC guard: OK"
|
|
170
|
+
|
|
171
|
+
- name: Joomla directory listing protection
|
|
172
|
+
if: steps.platform.outputs.platform == 'joomla'
|
|
173
|
+
run: |
|
|
174
|
+
MISSING=0
|
|
175
|
+
SOURCE_DIR="src"
|
|
176
|
+
[ ! -d "$SOURCE_DIR" ] && exit 0
|
|
177
|
+
while IFS= read -r dir; do
|
|
178
|
+
if [ ! -f "${dir}/index.html" ]; then
|
|
179
|
+
echo "::warning::Missing index.html in ${dir} (directory listing protection)"
|
|
180
|
+
MISSING=$((MISSING + 1))
|
|
181
|
+
fi
|
|
182
|
+
done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*")
|
|
183
|
+
if [ "$MISSING" -gt 0 ]; then
|
|
184
|
+
echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY
|
|
185
|
+
echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY
|
|
186
|
+
fi
|
|
187
|
+
echo "Directory protection: ${MISSING} missing (advisory)"
|
|
188
|
+
|
|
189
|
+
- name: Joomla script file and asset checks
|
|
190
|
+
if: steps.platform.outputs.platform == 'joomla'
|
|
191
|
+
run: |
|
|
192
|
+
ERRORS=0
|
|
193
|
+
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
|
194
|
+
[ -z "$MANIFEST" ] && exit 0
|
|
195
|
+
MANIFEST_DIR=$(dirname "$MANIFEST")
|
|
196
|
+
|
|
197
|
+
# Check scriptfile exists if declared
|
|
198
|
+
SCRIPTFILE=$(sed -n 's/.*<scriptfile>\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null)
|
|
199
|
+
if [ -n "$SCRIPTFILE" ]; then
|
|
200
|
+
if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then
|
|
201
|
+
echo "::error::Manifest declares <scriptfile>${SCRIPTFILE}</scriptfile> but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}"
|
|
202
|
+
ERRORS=$((ERRORS + 1))
|
|
203
|
+
else
|
|
204
|
+
echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)"
|
|
205
|
+
fi
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# Require joomla.asset.json and validate it
|
|
209
|
+
ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
210
|
+
if [ -z "$ASSET_JSON" ]; then
|
|
211
|
+
echo "::error::joomla.asset.json not found — Joomla asset system is required"
|
|
212
|
+
ERRORS=$((ERRORS + 1))
|
|
213
|
+
else
|
|
214
|
+
if command -v php &> /dev/null; then
|
|
215
|
+
php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || {
|
|
216
|
+
echo "::error::joomla.asset.json is not valid JSON"
|
|
217
|
+
ERRORS=$((ERRORS + 1))
|
|
218
|
+
}
|
|
219
|
+
fi
|
|
220
|
+
echo "joomla.asset.json: valid"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# Validate all XML files in src/ are well-formed
|
|
224
|
+
XML_ERRORS=0
|
|
225
|
+
if command -v php &> /dev/null; then
|
|
226
|
+
while IFS= read -r -d '' xmlfile; do
|
|
227
|
+
if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then
|
|
228
|
+
XML_ERRORS=$((XML_ERRORS + 1))
|
|
229
|
+
fi
|
|
230
|
+
done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0)
|
|
231
|
+
fi
|
|
232
|
+
if [ "$XML_ERRORS" -gt 0 ]; then
|
|
233
|
+
echo "::error::${XML_ERRORS} XML file(s) are malformed"
|
|
234
|
+
ERRORS=$((ERRORS + 1))
|
|
235
|
+
else
|
|
236
|
+
echo "XML well-formedness: OK"
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
[ "$ERRORS" -gt 0 ] && exit 1
|
|
240
|
+
echo "Joomla asset checks: OK"
|
|
241
|
+
|
|
242
|
+
- name: Validate platform manifest
|
|
243
|
+
run: |
|
|
244
|
+
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
245
|
+
case "$PLATFORM" in
|
|
246
|
+
joomla)
|
|
247
|
+
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
|
248
|
+
if [ -z "$MANIFEST" ]; then
|
|
249
|
+
echo "::warning::No Joomla manifest found (WaaS site)"
|
|
250
|
+
exit 0
|
|
251
|
+
fi
|
|
252
|
+
echo "Manifest: ${MANIFEST}"
|
|
253
|
+
if command -v php &> /dev/null; then
|
|
254
|
+
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
|
|
255
|
+
fi
|
|
256
|
+
for ELEMENT in name version description; do
|
|
257
|
+
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
|
258
|
+
done
|
|
259
|
+
# Block legacy raw/branch update server URLs on MokoGitea
|
|
260
|
+
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
|
|
261
|
+
if [ -n "$RAW_URLS" ]; then
|
|
262
|
+
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
|
|
263
|
+
echo "$RAW_URLS"
|
|
264
|
+
exit 1
|
|
265
|
+
fi
|
|
266
|
+
echo "Joomla manifest valid"
|
|
267
|
+
;;
|
|
268
|
+
dolibarr)
|
|
269
|
+
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
|
|
270
|
+
if [ -z "$MOD_FILE" ]; then
|
|
271
|
+
echo "::error::No mod*.class.php found"
|
|
272
|
+
exit 1
|
|
273
|
+
fi
|
|
274
|
+
echo "Dolibarr module: ${MOD_FILE}"
|
|
275
|
+
;;
|
|
276
|
+
*)
|
|
277
|
+
echo "Generic platform — no manifest validation"
|
|
278
|
+
;;
|
|
279
|
+
esac
|
|
280
|
+
|
|
281
|
+
- name: Check update stream format
|
|
282
|
+
run: |
|
|
283
|
+
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
284
|
+
case "$PLATFORM" in
|
|
285
|
+
joomla)
|
|
286
|
+
if [ -f "updates.xml" ]; then
|
|
287
|
+
if command -v php &> /dev/null; then
|
|
288
|
+
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; }
|
|
289
|
+
fi
|
|
290
|
+
echo "updates.xml valid"
|
|
291
|
+
fi
|
|
292
|
+
;;
|
|
293
|
+
dolibarr)
|
|
294
|
+
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt"
|
|
295
|
+
;;
|
|
296
|
+
esac
|
|
297
|
+
|
|
298
|
+
- name: Validate Joomla language files
|
|
299
|
+
if: steps.platform.outputs.platform == 'joomla'
|
|
300
|
+
run: |
|
|
301
|
+
ERRORS=0
|
|
302
|
+
WARNINGS=0
|
|
303
|
+
|
|
304
|
+
# Require both en-GB and en-US language directories
|
|
305
|
+
LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1)
|
|
306
|
+
if [ -z "$LANG_ROOT" ]; then
|
|
307
|
+
echo "No language/ directory found — skipping"
|
|
308
|
+
exit 0
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
if [ ! -d "$LANG_ROOT/en-GB" ]; then
|
|
312
|
+
echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)"
|
|
313
|
+
ERRORS=$((ERRORS + 1))
|
|
314
|
+
fi
|
|
315
|
+
if [ ! -d "$LANG_ROOT/en-US" ]; then
|
|
316
|
+
echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)"
|
|
317
|
+
ERRORS=$((ERRORS + 1))
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
# Check that en-GB and en-US have matching .ini files
|
|
321
|
+
if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then
|
|
322
|
+
for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do
|
|
323
|
+
[ ! -f "$GB_INI" ] && continue
|
|
324
|
+
US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")"
|
|
325
|
+
if [ ! -f "$US_INI" ]; then
|
|
326
|
+
echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US"
|
|
327
|
+
ERRORS=$((ERRORS + 1))
|
|
328
|
+
fi
|
|
329
|
+
done
|
|
330
|
+
for US_INI in "$LANG_ROOT/en-US"/*.ini; do
|
|
331
|
+
[ ! -f "$US_INI" ] && continue
|
|
332
|
+
GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")"
|
|
333
|
+
if [ ! -f "$GB_INI" ]; then
|
|
334
|
+
echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB"
|
|
335
|
+
ERRORS=$((ERRORS + 1))
|
|
336
|
+
fi
|
|
337
|
+
done
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
# Find all .ini language files
|
|
341
|
+
INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null)
|
|
342
|
+
if [ -z "$INI_FILES" ]; then
|
|
343
|
+
echo "No .ini language files found"
|
|
344
|
+
[ "$ERRORS" -gt 0 ] && exit 1
|
|
345
|
+
exit 0
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
echo "Found $(echo "$INI_FILES" | wc -l) language file(s)"
|
|
349
|
+
|
|
350
|
+
for FILE in $INI_FILES; do
|
|
351
|
+
FNAME=$(basename "$FILE")
|
|
352
|
+
LINENUM=0
|
|
353
|
+
SEEN_KEYS=""
|
|
354
|
+
|
|
355
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
356
|
+
LINENUM=$((LINENUM + 1))
|
|
357
|
+
|
|
358
|
+
# Skip empty lines and comments
|
|
359
|
+
[ -z "$line" ] && continue
|
|
360
|
+
echo "$line" | grep -qE '^\s*;' && continue
|
|
361
|
+
echo "$line" | grep -qE '^\s*$' && continue
|
|
362
|
+
|
|
363
|
+
# Must match KEY="VALUE" format
|
|
364
|
+
if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then
|
|
365
|
+
echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}"
|
|
366
|
+
ERRORS=$((ERRORS + 1))
|
|
367
|
+
continue
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
# Extract key and check for duplicates
|
|
371
|
+
KEY=$(echo "$line" | sed 's/=.*//')
|
|
372
|
+
if echo "$SEEN_KEYS" | grep -qx "$KEY"; then
|
|
373
|
+
echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}"
|
|
374
|
+
ERRORS=$((ERRORS + 1))
|
|
375
|
+
fi
|
|
376
|
+
SEEN_KEYS="${SEEN_KEYS}
|
|
377
|
+
${KEY}"
|
|
378
|
+
done < "$FILE"
|
|
379
|
+
|
|
380
|
+
echo " ${FILE}: checked ${LINENUM} lines"
|
|
381
|
+
done
|
|
382
|
+
|
|
383
|
+
# Cross-check en-GB vs en-US key consistency
|
|
384
|
+
GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1)
|
|
385
|
+
US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1)
|
|
386
|
+
|
|
387
|
+
if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then
|
|
388
|
+
for GB_FILE in "$GB_DIR"/*.ini; do
|
|
389
|
+
[ ! -f "$GB_FILE" ] && continue
|
|
390
|
+
FNAME=$(basename "$GB_FILE")
|
|
391
|
+
US_FILE="$US_DIR/$FNAME"
|
|
392
|
+
[ ! -f "$US_FILE" ] && continue
|
|
393
|
+
|
|
394
|
+
GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort)
|
|
395
|
+
US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort)
|
|
396
|
+
|
|
397
|
+
# Keys in en-GB but not en-US
|
|
398
|
+
MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
|
|
399
|
+
if [ -n "$MISSING_US" ]; then
|
|
400
|
+
echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:"
|
|
401
|
+
echo "$MISSING_US" | while read -r k; do echo " - $k"; done
|
|
402
|
+
WARNINGS=$((WARNINGS + 1))
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
# Keys in en-US but not en-GB
|
|
406
|
+
MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
|
|
407
|
+
if [ -n "$MISSING_GB" ]; then
|
|
408
|
+
echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:"
|
|
409
|
+
echo "$MISSING_GB" | while read -r k; do echo " - $k"; done
|
|
410
|
+
WARNINGS=$((WARNINGS + 1))
|
|
411
|
+
fi
|
|
412
|
+
done
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
{
|
|
416
|
+
echo "### Language File Validation"
|
|
417
|
+
echo "| Metric | Count |"
|
|
418
|
+
echo "|---|---|"
|
|
419
|
+
echo "| Files checked | $(echo "$INI_FILES" | wc -l) |"
|
|
420
|
+
echo "| Errors | ${ERRORS} |"
|
|
421
|
+
echo "| Warnings | ${WARNINGS} |"
|
|
422
|
+
} >> $GITHUB_STEP_SUMMARY
|
|
423
|
+
|
|
424
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
425
|
+
echo "::error::Language validation failed with ${ERRORS} error(s)"
|
|
426
|
+
exit 1
|
|
427
|
+
fi
|
|
428
|
+
echo "Language files: OK (${WARNINGS} warning(s))"
|
|
429
|
+
|
|
430
|
+
- name: Check changelog has unreleased entry
|
|
431
|
+
run: |
|
|
432
|
+
if [ ! -f "CHANGELOG.md" ]; then
|
|
433
|
+
echo "::warning::No CHANGELOG.md found"
|
|
434
|
+
exit 0
|
|
435
|
+
fi
|
|
436
|
+
# Check for content under [Unreleased] section
|
|
437
|
+
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then
|
|
438
|
+
echo "::error::CHANGELOG.md missing [Unreleased] section"
|
|
439
|
+
exit 1
|
|
440
|
+
fi
|
|
441
|
+
# Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased
|
|
442
|
+
UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true)
|
|
443
|
+
if [ "$UNRELEASED_CONTENT" -eq 0 ]; then
|
|
444
|
+
echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes."
|
|
445
|
+
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY
|
|
446
|
+
echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY
|
|
447
|
+
echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY
|
|
448
|
+
exit 1
|
|
449
|
+
fi
|
|
450
|
+
echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]"
|
|
451
|
+
|
|
452
|
+
- name: Verify package source
|
|
453
|
+
run: |
|
|
454
|
+
SOURCE_DIR="src"
|
|
455
|
+
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
|
456
|
+
if [ ! -d "$SOURCE_DIR" ]; then
|
|
457
|
+
echo "::warning::No src/ or htdocs/ directory"
|
|
458
|
+
exit 0
|
|
459
|
+
fi
|
|
460
|
+
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
|
|
461
|
+
echo "Source: ${FILE_COUNT} files"
|
|
462
|
+
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
|
|
463
|
+
|
|
464
|
+
# ── Pre-Release RC Build ─────────────────────────────────────────────────
|
|
465
|
+
pre-release:
|
|
466
|
+
name: Build RC Package
|
|
467
|
+
runs-on: ubuntu-latest
|
|
468
|
+
needs: [branch-policy, validate]
|
|
469
|
+
|
|
470
|
+
steps:
|
|
471
|
+
- name: Trigger RC pre-release
|
|
472
|
+
env:
|
|
473
|
+
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
474
|
+
REPO: ${{ github.repository }}
|
|
475
|
+
BRANCH: ${{ github.head_ref }}
|
|
476
|
+
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
477
|
+
run: |
|
|
478
|
+
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
|
479
|
+
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
|
480
|
+
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
|
481
|
+
|
|
482
|
+
# ── Issue Reporter ──────────────────────────────────────────────────────
|
|
483
|
+
report-issues:
|
|
484
|
+
name: Report Issues
|
|
485
|
+
runs-on: ubuntu-latest
|
|
486
|
+
needs: [branch-policy, validate]
|
|
487
|
+
if: >-
|
|
488
|
+
always() &&
|
|
489
|
+
needs.validate.result == 'failure'
|
|
490
|
+
|
|
491
|
+
steps:
|
|
492
|
+
- name: Checkout
|
|
493
|
+
uses: actions/checkout@v4
|
|
494
|
+
with:
|
|
495
|
+
sparse-checkout: automation/ci-issue-reporter.sh
|
|
496
|
+
sparse-checkout-cone-mode: false
|
|
497
|
+
|
|
498
|
+
- name: "File issue for PR validation failure"
|
|
499
|
+
env:
|
|
500
|
+
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
501
|
+
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
502
|
+
run: |
|
|
503
|
+
chmod +x automation/ci-issue-reporter.sh
|
|
504
|
+
./automation/ci-issue-reporter.sh \
|
|
505
|
+
--gate "PR Validation" \
|
|
506
|
+
--workflow "PR Check" \
|
|
507
|
+
--severity error \
|
|
508
|
+
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
|
@@ -0,0 +1,11 @@
|
|
|
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: Gitea.Workflow
|
|
7
|
+
# INGROUP: moko-platform.Release
|
|
8
|
+
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
9
|
+
# PATH: /templates/workflows/universal/pre-release.yml.template
|
|
10
|
+
# VERSION: 05.01.00
|
|
11
|
+
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|