@mokoconsulting/mcp-mokosuite 1.1.0-dev.372 → 1.1.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/.mokogit/workflows/auto-release.yml +142 -24
- package/.mokogit/workflows/cleanup.yml +4 -3
- package/.mokogit/workflows/issue-branch.yml +1 -1
- package/.mokogit/workflows/npm-build-test.yml +310 -28
- package/.mokogit/workflows/npm-publish.yml +98 -109
- package/.mokogit/workflows/pre-release.yml +20 -3
- package/.mokogit/workflows/rc-revert.yml +4 -1
- package/CHANGELOG.md +0 -17
- package/Makefile +72 -48
- package/dist/index.js +1 -28
- package/package.json +1 -1
- package/src/index.ts +1 -59
- package/.mokogit/workflows/cascade-dev.yml +0 -266
- package/.mokogit/workflows/changelog-validation.yml +0 -117
- package/.mokogit/workflows/custom-workflow-sync-trigger.yml +0 -83
- package/.mokogit/workflows/gitleaks.yml +0 -94
- package/.mokogit/workflows/notify.yml +0 -73
- package/.mokogit/workflows/pr-check.yml +0 -693
- package/.mokogit/workflows/push-notify.yml +0 -43
- package/.mokogit/workflows/sync-version-on-merge.yml +0 -130
- package/.mokogit/workflows/version-set.yml +0 -131
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
# (moko lint|build|test), then validates that compiled output exists and that
|
|
7
7
|
# tools are properly registered. The build agent is platform-aware via the
|
|
8
8
|
# repo's NATIVE manifest (package.json) — no metadata API call — so inline
|
|
9
|
-
#
|
|
9
|
+
# Deps are installed via `npm ci` below so `moko lint|build|test` (which run
|
|
10
|
+
# `npx --no-install tsc`/eslint) can find typescript/eslint from devDependencies.
|
|
10
11
|
#
|
|
11
12
|
# GATE: `moko` is provided by mokocli on the runner PATH (/opt/mokocli/cli).
|
|
12
13
|
# It becomes available only after the mokocli build agent lands on `main` and
|
|
@@ -21,12 +22,14 @@ on:
|
|
|
21
22
|
paths: ['source/**', 'src/**', 'package.json', 'tsconfig.json']
|
|
22
23
|
pull_request:
|
|
23
24
|
branches: [main]
|
|
24
|
-
paths:
|
|
25
|
+
# No paths filter: the folded-in universal pr-check jobs (Branch Policy,
|
|
26
|
+
# Require Docs, Wiki Reminder) must run on EVERY PR.
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
permissions:
|
|
28
30
|
contents: read
|
|
29
31
|
pull-requests: write
|
|
32
|
+
issues: write # folded-in Wiki Reminder posts a sticky PR comment
|
|
30
33
|
|
|
31
34
|
env:
|
|
32
35
|
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
@@ -56,6 +59,10 @@ jobs:
|
|
|
56
59
|
with:
|
|
57
60
|
node-version: ${{ matrix.node-version }}
|
|
58
61
|
|
|
62
|
+
- name: Install npm dependencies
|
|
63
|
+
run: |
|
|
64
|
+
if [ -f package-lock.json ]; then npm ci; else npm install --no-audit --no-fund; fi
|
|
65
|
+
|
|
59
66
|
- name: Setup MokoCLI build agent (moko on PATH)
|
|
60
67
|
env:
|
|
61
68
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
@@ -128,40 +135,315 @@ jobs:
|
|
|
128
135
|
exit 1
|
|
129
136
|
fi
|
|
130
137
|
|
|
131
|
-
ci-summary:
|
|
132
|
-
|
|
138
|
+
# NOTE: a standalone `ci-summary` job (needs: [build], if: always()) was removed.
|
|
139
|
+
# This Gitea act_runner never schedules a `needs:`+`if: always()` job — it parks in
|
|
140
|
+
# "waiting" (runner_id 0) forever and never dispatches. Under the org-governance
|
|
141
|
+
# `main` rule (status_check_contexts: ["*"], every check must be green) that stranded
|
|
142
|
+
# job was a permanent, silent block on every PR into main. The per-job status is
|
|
143
|
+
# already visible on the Actions run page and `wiki-reminder` posts a sticky PR
|
|
144
|
+
# comment on main PRs, so a separate summary job earned its keep only when it worked —
|
|
145
|
+
# which, on this runner, was never. If a build-result summary comment is wanted back,
|
|
146
|
+
# add it as a final STEP with `if: ${{ always() }}` inside the `build` job (step-level
|
|
147
|
+
# always() IS supported), not as a dependent job. (Matches ci-go.yml.)
|
|
148
|
+
|
|
149
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
150
|
+
# Universal PR-check jobs — folded in from "Universal: PR Check" so the NPM
|
|
151
|
+
# platform runs one CI workflow per PR (mirrors Template-Joomla #162).
|
|
152
|
+
# Branch-gated: Branch Policy on all PRs; docs gate + wiki reminder on main.
|
|
153
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
154
|
+
|
|
155
|
+
branch-policy:
|
|
156
|
+
name: Branch Policy
|
|
157
|
+
runs-on: ubuntu-latest
|
|
158
|
+
steps:
|
|
159
|
+
- name: Check branch merge target
|
|
160
|
+
run: |
|
|
161
|
+
HEAD="${{ github.head_ref }}"
|
|
162
|
+
BASE="${{ github.base_ref }}"
|
|
163
|
+
|
|
164
|
+
echo "PR: ${HEAD} → ${BASE}"
|
|
165
|
+
|
|
166
|
+
ALLOWED=true
|
|
167
|
+
REASON=""
|
|
168
|
+
|
|
169
|
+
case "$HEAD" in
|
|
170
|
+
feature/*|feat/*)
|
|
171
|
+
if [ "$BASE" != "dev" ]; then
|
|
172
|
+
ALLOWED=false
|
|
173
|
+
REASON="Feature branches must target 'dev', not '${BASE}'"
|
|
174
|
+
fi
|
|
175
|
+
;;
|
|
176
|
+
fix/*|bugfix/*)
|
|
177
|
+
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
|
178
|
+
ALLOWED=false
|
|
179
|
+
REASON="Fix branches must target 'dev' or 'main', not '${BASE}'"
|
|
180
|
+
fi
|
|
181
|
+
;;
|
|
182
|
+
patch/*)
|
|
183
|
+
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ] && [ "$BASE" != "main" ]; then
|
|
184
|
+
ALLOWED=false
|
|
185
|
+
REASON="Patch branches must target 'dev', 'rc', or 'main', not '${BASE}'"
|
|
186
|
+
fi
|
|
187
|
+
;;
|
|
188
|
+
hotfix/*)
|
|
189
|
+
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
|
190
|
+
ALLOWED=false
|
|
191
|
+
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
|
192
|
+
fi
|
|
193
|
+
;;
|
|
194
|
+
rc)
|
|
195
|
+
if [ "$BASE" != "main" ]; then
|
|
196
|
+
ALLOWED=false
|
|
197
|
+
REASON="RC branch can only merge into 'main', not '${BASE}'"
|
|
198
|
+
fi
|
|
199
|
+
;;
|
|
200
|
+
dev)
|
|
201
|
+
if [ "$BASE" != "main" ]; then
|
|
202
|
+
ALLOWED=false
|
|
203
|
+
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
|
204
|
+
fi
|
|
205
|
+
;;
|
|
206
|
+
esac
|
|
207
|
+
|
|
208
|
+
if [ "$ALLOWED" = false ]; then
|
|
209
|
+
echo "::error::${REASON}"
|
|
210
|
+
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
|
211
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
212
|
+
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
|
213
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
214
|
+
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
|
215
|
+
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
|
216
|
+
echo "- \`fix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
217
|
+
echo "- \`patch/*\` → \`dev\`, \`rc\`, or \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
218
|
+
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
219
|
+
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
220
|
+
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
221
|
+
exit 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
|
225
|
+
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
|
226
|
+
|
|
227
|
+
# ── Docs Update Gate (wiki-aware) ───────────────────────────────────────
|
|
228
|
+
#
|
|
229
|
+
# RULE (in order):
|
|
230
|
+
# 1. Short-circuit: if the PR diff touches CHANGELOG.md OR README.md, PASS
|
|
231
|
+
# immediately (deterministic; no wiki lookup).
|
|
232
|
+
# 2. Else consult the repo WIKI (<owner>/<repo>.wiki.git) HEAD commit date.
|
|
233
|
+
# Mode is chosen by the org/repo variable DOCS_WIKI_WINDOW (default 'pr'):
|
|
234
|
+
# - 'pr' → satisfied if wiki HEAD commit date >= this PR's
|
|
235
|
+
# created_at (docs touched since the PR opened).
|
|
236
|
+
# - '<N>h'/'<N>d' → satisfied if wiki HEAD commit is within that rolling
|
|
237
|
+
# window (now - duration). e.g. 24h, 72h, 7d.
|
|
238
|
+
# Fail-open: if the wiki is absent/unreachable or the lookup errors, do
|
|
239
|
+
# NOT hard-fail — treat as "unsatisfied → warn" per step 3.
|
|
240
|
+
# 3. Verdict: if neither is satisfied → emit ::warning:: (advisory) and PASS
|
|
241
|
+
# on non-main branches; hard-fail (exit 1) ONLY when the PR base is 'main'
|
|
242
|
+
# (release hygiene on the stable line).
|
|
243
|
+
#
|
|
244
|
+
# The job runs on EVERY PR so the required status context "Require Docs Update"
|
|
245
|
+
# always reports; the base-branch gate is enforced inside the step, not via a
|
|
246
|
+
# job-level `if:` that would leave the context missing on non-main PRs.
|
|
247
|
+
require-docs:
|
|
248
|
+
name: Require Docs Update
|
|
249
|
+
runs-on: ubuntu-latest
|
|
250
|
+
steps:
|
|
251
|
+
- name: Checkout
|
|
252
|
+
uses: actions/checkout@v4
|
|
253
|
+
with:
|
|
254
|
+
fetch-depth: 0
|
|
255
|
+
|
|
256
|
+
- name: Docs gate (docs diff, else wiki freshness)
|
|
257
|
+
env:
|
|
258
|
+
BASE_REF: ${{ github.base_ref }}
|
|
259
|
+
HEAD_REF: ${{ github.head_ref }}
|
|
260
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
261
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
262
|
+
PR_CREATED_AT: ${{ github.event.pull_request.created_at }}
|
|
263
|
+
SERVER: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
264
|
+
REPO: ${{ github.repository }}
|
|
265
|
+
TOKEN: ${{ github.token }}
|
|
266
|
+
DOCS_WIKI_WINDOW: ${{ vars.DOCS_WIKI_WINDOW || 'pr' }}
|
|
267
|
+
run: |
|
|
268
|
+
set -uo pipefail
|
|
269
|
+
|
|
270
|
+
# ── Step 1: docs diff short-circuit ─────────────────────────────
|
|
271
|
+
CHANGED="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" 2>/dev/null || true)"
|
|
272
|
+
if [ -z "$CHANGED" ]; then
|
|
273
|
+
git fetch -q origin "$BASE_REF" 2>/dev/null || true
|
|
274
|
+
CHANGED="$(git diff --name-only "origin/${BASE_REF}...HEAD" 2>/dev/null || true)"
|
|
275
|
+
fi
|
|
276
|
+
echo "Changed files in PR:"
|
|
277
|
+
echo "$CHANGED"
|
|
278
|
+
if echo "$CHANGED" | grep -qxE 'CHANGELOG\.md|README\.md'; then
|
|
279
|
+
echo "Docs diff present (CHANGELOG.md or README.md) — gate satisfied."
|
|
280
|
+
echo "## Docs Update: Passed (docs diff)" >> "$GITHUB_STEP_SUMMARY"
|
|
281
|
+
exit 0
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# ── Step 2: wiki freshness (fail-open) ──────────────────────────
|
|
285
|
+
# Resolve wiki HEAD commit epoch via a shallow clone of the wiki repo.
|
|
286
|
+
# Any failure here is non-fatal: SATISFIED stays 0 → handled by step 3.
|
|
287
|
+
SATISFIED=0
|
|
288
|
+
WIKI_EPOCH=""
|
|
289
|
+
TOK="$(printf '%s' "${TOKEN:-}" | tr -d '[:space:]')"
|
|
290
|
+
HOSTPATH="${SERVER#https://}"; HOSTPATH="${HOSTPATH#http://}"
|
|
291
|
+
WIKI_URL="https://x-access-token:${TOK}@${HOSTPATH}/${REPO}.wiki.git"
|
|
292
|
+
echo "::add-mask::${TOK}"
|
|
293
|
+
rm -rf /tmp/docs-wiki
|
|
294
|
+
if git clone --depth 1 --quiet "${WIKI_URL}" /tmp/docs-wiki 2>/dev/null; then
|
|
295
|
+
WIKI_EPOCH="$(git -C /tmp/docs-wiki log -1 --format=%ct 2>/dev/null || true)"
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
if [ -z "$WIKI_EPOCH" ]; then
|
|
299
|
+
echo "::warning::wiki absent/unreachable — treating docs gate as unsatisfied (fail-open)"
|
|
300
|
+
else
|
|
301
|
+
MODE="$(printf '%s' "${DOCS_WIKI_WINDOW}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
302
|
+
[ -n "$MODE" ] || MODE="pr"
|
|
303
|
+
if [ "$MODE" = "pr" ]; then
|
|
304
|
+
# PASS if wiki HEAD commit date >= PR created_at.
|
|
305
|
+
PR_EPOCH="$(date -u -d "${PR_CREATED_AT}" +%s 2>/dev/null || true)"
|
|
306
|
+
if [ -z "$PR_EPOCH" ]; then
|
|
307
|
+
echo "::warning::could not parse PR created_at ('${PR_CREATED_AT}') — docs gate unsatisfied (fail-open)"
|
|
308
|
+
elif [ "$WIKI_EPOCH" -ge "$PR_EPOCH" ]; then
|
|
309
|
+
SATISFIED=1
|
|
310
|
+
echo "Wiki updated since PR opened (wiki=${WIKI_EPOCH} >= pr=${PR_EPOCH})."
|
|
311
|
+
else
|
|
312
|
+
echo "Wiki not updated since PR opened (wiki=${WIKI_EPOCH} < pr=${PR_EPOCH})."
|
|
313
|
+
fi
|
|
314
|
+
else
|
|
315
|
+
# Duration window: <N>h / <N>d (also bare <N> = seconds).
|
|
316
|
+
NUM="$(printf '%s' "$MODE" | sed -E 's/[^0-9].*$//')"
|
|
317
|
+
UNIT="$(printf '%s' "$MODE" | sed -E 's/^[0-9]+//')"
|
|
318
|
+
SECS=0
|
|
319
|
+
case "$UNIT" in
|
|
320
|
+
h) SECS=$(( NUM * 3600 )) ;;
|
|
321
|
+
d) SECS=$(( NUM * 86400 )) ;;
|
|
322
|
+
m) SECS=$(( NUM * 60 )) ;;
|
|
323
|
+
s|"") SECS=$(( NUM )) ;;
|
|
324
|
+
*) echo "::warning::unrecognized DOCS_WIKI_WINDOW unit '${UNIT}'; falling back to 'pr'-style (>= PR created_at)"
|
|
325
|
+
PR_EPOCH="$(date -u -d "${PR_CREATED_AT}" +%s 2>/dev/null || echo 0)"
|
|
326
|
+
SECS=0 ;;
|
|
327
|
+
esac
|
|
328
|
+
if [ "$SECS" -gt 0 ]; then
|
|
329
|
+
NOW="$(date -u +%s)"
|
|
330
|
+
CUTOFF=$(( NOW - SECS ))
|
|
331
|
+
if [ "$WIKI_EPOCH" -ge "$CUTOFF" ]; then
|
|
332
|
+
SATISFIED=1
|
|
333
|
+
echo "Wiki updated within window ${MODE} (wiki=${WIKI_EPOCH} >= cutoff=${CUTOFF})."
|
|
334
|
+
else
|
|
335
|
+
echo "Wiki stale beyond window ${MODE} (wiki=${WIKI_EPOCH} < cutoff=${CUTOFF})."
|
|
336
|
+
fi
|
|
337
|
+
elif [ "${PR_EPOCH:-0}" -gt 0 ] && [ "$WIKI_EPOCH" -ge "${PR_EPOCH}" ]; then
|
|
338
|
+
SATISFIED=1
|
|
339
|
+
fi
|
|
340
|
+
fi
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
# ── Step 3: verdict ─────────────────────────────────────────────
|
|
344
|
+
if [ "$SATISFIED" = "1" ]; then
|
|
345
|
+
echo "## Docs Update: Passed (wiki fresh)" >> "$GITHUB_STEP_SUMMARY"
|
|
346
|
+
exit 0
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
if [ "$BASE_REF" = "main" ]; then
|
|
350
|
+
echo "::error::Docs gate unsatisfied: no CHANGELOG.md/README.md change and wiki not fresh (DOCS_WIKI_WINDOW='${DOCS_WIKI_WINDOW}'). Required on PRs into main."
|
|
351
|
+
{
|
|
352
|
+
echo "## Docs Update Required"
|
|
353
|
+
echo ""
|
|
354
|
+
echo "PRs merging into \`main\` must either update **CHANGELOG.md** or **README.md**, or the repo **wiki** must be fresh (mode: \`${DOCS_WIKI_WINDOW}\`)."
|
|
355
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
356
|
+
exit 1
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
echo "::warning::Docs gate unsatisfied (advisory on non-main branch '${BASE_REF}'): update CHANGELOG.md/README.md or the wiki."
|
|
360
|
+
{
|
|
361
|
+
echo "## Docs Update: Advisory"
|
|
362
|
+
echo ""
|
|
363
|
+
echo "No docs diff and wiki not fresh (mode: \`${DOCS_WIKI_WINDOW}\`). Non-blocking on \`${BASE_REF}\`; will hard-fail on \`main\`."
|
|
364
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
365
|
+
exit 0
|
|
366
|
+
|
|
367
|
+
wiki-reminder:
|
|
368
|
+
name: Wiki Update Reminder
|
|
133
369
|
runs-on: ubuntu-latest
|
|
134
|
-
|
|
135
|
-
if: always()
|
|
370
|
+
if: ${{ github.base_ref == 'main' }}
|
|
136
371
|
steps:
|
|
137
|
-
- name:
|
|
372
|
+
- name: Remind to update the wiki
|
|
138
373
|
env:
|
|
139
|
-
TOKEN: ${{
|
|
374
|
+
TOKEN: ${{ github.token }}
|
|
140
375
|
SERVER: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
141
376
|
REPO: ${{ github.repository }}
|
|
142
377
|
PR: ${{ github.event.pull_request.number }}
|
|
143
|
-
BUILD: ${{ needs.build.result }}
|
|
144
|
-
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
145
|
-
HEAD_REF: ${{ github.head_ref }}
|
|
146
|
-
BASE_REF: ${{ github.base_ref }}
|
|
147
378
|
run: |
|
|
148
379
|
set -uo pipefail
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"
|
|
154
|
-
|
|
155
|
-
|
|
380
|
+
{
|
|
381
|
+
echo "## Wiki Update Reminder"
|
|
382
|
+
echo ""
|
|
383
|
+
echo "Docs are **wiki-first** at MokoConsulting. If this change affects behavior, usage, configuration, or standards, update the repo wiki:"
|
|
384
|
+
echo ""
|
|
385
|
+
echo "- ${SERVER}/${REPO}/wiki"
|
|
386
|
+
echo ""
|
|
387
|
+
echo "_Non-blocking reminder._"
|
|
388
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
389
|
+
# Sticky PR comment: create-or-UPDATE a single comment keyed by a hidden
|
|
390
|
+
# marker (no duplicate reminders across pushes). Best-effort, never fails.
|
|
156
391
|
LIST="${SERVER}/api/v1/repos/${REPO}/issues/${PR}/comments"
|
|
157
392
|
EDIT="${SERVER}/api/v1/repos/${REPO}/issues/comments"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
393
|
+
MARKER="<!-- wiki-reminder -->"
|
|
394
|
+
BODY="$(printf '%s\n\n%s' "${MARKER}" "**Wiki reminder:** docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. _(non-blocking)_")"
|
|
395
|
+
if [ -n "${TOKEN:-}" ] && [ -n "${PR:-}" ]; then
|
|
396
|
+
CID="$(curl -sf -H "Authorization: token ${TOKEN}" "${LIST}?limit=100" 2>/dev/null \
|
|
397
|
+
| python3 -c "import sys,json; c=[x for x in json.load(sys.stdin) if '${MARKER}' in (x.get('body') or '')]; print(c[-1]['id'] if c else '')" 2>/dev/null || true)"
|
|
398
|
+
PAYLOAD="$(python3 -c "import json,sys; print(json.dumps({'body': sys.stdin.read()}))" <<<"${BODY}")"
|
|
399
|
+
if [ -n "${CID}" ]; then
|
|
400
|
+
curl -sf -X PATCH -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -d "${PAYLOAD}" "${EDIT}/${CID}" >/dev/null 2>&1 || true
|
|
401
|
+
else
|
|
402
|
+
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -d "${PAYLOAD}" "${LIST}" >/dev/null 2>&1 || true
|
|
403
|
+
fi
|
|
404
|
+
fi
|
|
405
|
+
echo "Wiki reminder emitted (non-blocking, sticky)."
|
|
406
|
+
|
|
407
|
+
# ── Universal PR gate checks (folded from pr-check's Validate PR) ─────────
|
|
408
|
+
pr-gate-checks:
|
|
409
|
+
name: PR Gate Checks
|
|
410
|
+
runs-on: ubuntu-latest
|
|
411
|
+
steps:
|
|
412
|
+
- name: Checkout
|
|
413
|
+
uses: actions/checkout@v4
|
|
414
|
+
|
|
415
|
+
- name: Check for merge conflict markers
|
|
416
|
+
run: |
|
|
417
|
+
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --exclude-dir='.git' --exclude-dir='.mokogit' --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)
|
|
418
|
+
if [ -n "$CONFLICTS" ]; then
|
|
419
|
+
echo "::error::Merge conflict markers found in source files"
|
|
420
|
+
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
|
|
421
|
+
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
422
|
+
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
|
423
|
+
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
424
|
+
exit 1
|
|
425
|
+
fi
|
|
426
|
+
echo "No conflict markers found"
|
|
427
|
+
|
|
428
|
+
- name: Reject .gitea and .github workflow dirs
|
|
429
|
+
run: |
|
|
430
|
+
# .gitea/ and .github/ carry their own workflows that CONFLICT with the
|
|
431
|
+
# MokoGIT .mokogit/workflows/ system (duplicate / competing runs). They
|
|
432
|
+
# must never exist in-repo. Fail the PR if either directory is tracked so
|
|
433
|
+
# it gets deleted.
|
|
434
|
+
HITS=$(git ls-files | grep -E '^(\.gitea|\.github)/' || true)
|
|
435
|
+
if [ -n "$HITS" ]; then
|
|
436
|
+
echo "::error::.gitea/ and .github/ are not allowed - their workflows conflict with .mokogit/workflows/. Delete these directories."
|
|
437
|
+
printf '%s\n' "$HITS" | while IFS= read -r f; do
|
|
438
|
+
[ -n "$f" ] && echo "::error file=${f}::Remove ${f} (.gitea/.github workflows conflict with .mokogit/)"
|
|
439
|
+
done
|
|
440
|
+
{
|
|
441
|
+
echo "## Blocked: .gitea/.github present"
|
|
442
|
+
echo "These directories conflict with the MokoGIT \`.mokogit/workflows/\` system and must be deleted:"
|
|
443
|
+
echo '```'
|
|
444
|
+
printf '%s\n' "$HITS"
|
|
445
|
+
echo '```'
|
|
446
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
447
|
+
exit 1
|
|
167
448
|
fi
|
|
449
|
+
echo "No .gitea/.github dirs - OK"
|
|
@@ -7,19 +7,14 @@
|
|
|
7
7
|
# INGROUP: MokoCLI.Release
|
|
8
8
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-NPM
|
|
9
9
|
# PATH: /.mokogit/workflows/npm-publish.yml
|
|
10
|
-
# VERSION:
|
|
11
|
-
# BRIEF: Build + publish shim.
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# npm registry) that the former inline block performed.
|
|
19
|
-
#
|
|
20
|
-
# GATE: `moko` is provided by mokocli on the runner PATH (/opt/mokocli/cli),
|
|
21
|
-
# available only after the mokocli build agent lands on `main` and the runner
|
|
22
|
-
# image is rebuilt — authored/reviewed now, runs green later.
|
|
10
|
+
# VERSION: 04.00.01
|
|
11
|
+
# BRIEF: Build + publish shim. Version comes from the root Makefile
|
|
12
|
+
# (PROJECT_VERSION — the fleet-wide source of truth); this workflow never
|
|
13
|
+
# bumps it. It builds with plain npm and does an idempotent DUAL-REGISTRY
|
|
14
|
+
# publish: the MokoGIT npm registry using the per-run Actions token
|
|
15
|
+
# (GITHUB_TOKEN + packages:write — no shared MOKOGIT_TOKEN), and npmjs.org
|
|
16
|
+
# using NPM_TOKEN (best-effort; skipped if the secret is absent). A
|
|
17
|
+
# version already present on a registry is a no-op (idempotent).
|
|
23
18
|
|
|
24
19
|
name: "Publish to npm"
|
|
25
20
|
|
|
@@ -29,125 +24,119 @@ on:
|
|
|
29
24
|
- main
|
|
30
25
|
workflow_dispatch:
|
|
31
26
|
|
|
27
|
+
# The MokoGIT-registry publish uses the run's own token instead of a shared
|
|
28
|
+
# org secret: least-privilege, auto-expiring, no per-repo provisioning.
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
packages: write
|
|
32
|
+
|
|
33
|
+
env:
|
|
34
|
+
MOKOGIT_HOST: git.mokoconsulting.tech
|
|
35
|
+
|
|
32
36
|
jobs:
|
|
33
37
|
publish:
|
|
38
|
+
name: "Build & dual-publish"
|
|
34
39
|
runs-on: release
|
|
40
|
+
# Skip template repos, and self-pushes the release engine marks [skip ci]
|
|
41
|
+
# (its version-bump/changelog commits) or [skip publish].
|
|
35
42
|
if: >-
|
|
43
|
+
!startsWith(github.event.repository.name, 'Template-') &&
|
|
36
44
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
|
37
|
-
!contains(github.event.head_commit.message, '[skip publish]')
|
|
38
|
-
!startsWith(github.event.repository.name, 'Template-')
|
|
45
|
+
!contains(github.event.head_commit.message, '[skip publish]')
|
|
39
46
|
steps:
|
|
40
47
|
- name: Checkout
|
|
41
48
|
uses: actions/checkout@v4
|
|
42
49
|
with:
|
|
43
|
-
fetch-depth:
|
|
50
|
+
fetch-depth: 1
|
|
44
51
|
|
|
45
|
-
- name:
|
|
46
|
-
|
|
47
|
-
uses: ./.mokogit/actions/resolve-source-dir
|
|
52
|
+
- name: Setup Node.js
|
|
53
|
+
uses: actions/setup-node@v4
|
|
48
54
|
with:
|
|
49
|
-
|
|
50
|
-
strict: 'true'
|
|
55
|
+
node-version: '20'
|
|
51
56
|
|
|
52
|
-
- name:
|
|
53
|
-
id:
|
|
57
|
+
- name: Resolve version from the Makefile
|
|
58
|
+
id: v
|
|
54
59
|
run: |
|
|
55
60
|
set -euo pipefail
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
echo "
|
|
61
|
-
exit 0
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
# Build the source-dir alternative from the resolved dir (escape ERE metachars).
|
|
65
|
-
# Class lists ] first and [ last to avoid a [. collating-symbol parse; the
|
|
66
|
-
# \\\\& replacement reaches sed as \\& = literal backslash + matched char.
|
|
67
|
-
SRC_DIR='${{ steps.srcdir.outputs.source_dir }}'
|
|
68
|
-
SRC_RE=$(printf '%s' "$SRC_DIR" | sed 's/[].^$*+?(){}|[]/\\\\&/g')
|
|
69
|
-
PATTERN="^(${SRC_RE}|package\.json$|package-lock\.json$|tsconfig\.json$|README\.md$)"
|
|
70
|
-
echo "Detect pattern: ${PATTERN}"
|
|
71
|
-
|
|
72
|
-
BEFORE="${{ github.event.before }}"
|
|
73
|
-
if [ -z "${BEFORE}" ] || ! git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
|
|
74
|
-
BEFORE=$(git rev-parse HEAD^ 2>/dev/null || echo "")
|
|
75
|
-
fi
|
|
76
|
-
if [ -n "${BEFORE}" ]; then
|
|
77
|
-
FILES=$(git diff --name-only "${BEFORE}" HEAD)
|
|
78
|
-
else
|
|
79
|
-
FILES=$(git show --name-only --pretty=format: HEAD)
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
if printf '%s\n' "${FILES}" | grep -Eq "${PATTERN}"; then
|
|
83
|
-
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
84
|
-
else
|
|
85
|
-
echo "::notice::No publishable changes under '${SRC_DIR}'. Files: ${FILES}"
|
|
86
|
-
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
61
|
+
# PROJECT_VERSION in the root Makefile is the single source of truth
|
|
62
|
+
# (auto-filled per repo by workflow sync). Everything else derives from it.
|
|
63
|
+
VERSION=$(grep -E '^[[:space:]]*PROJECT_VERSION[[:space:]]*:?=' Makefile | head -1 | sed 's/.*=[[:space:]]*//' | tr -d '[:space:]')
|
|
64
|
+
if [ -z "$VERSION" ]; then
|
|
65
|
+
echo "::error::No PROJECT_VERSION found in Makefile — cannot determine publish version."; exit 1
|
|
87
66
|
fi
|
|
67
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
68
|
+
# Stamp package.json to match, IN-TREE ONLY (never committed) so `npm publish`
|
|
69
|
+
# ships exactly the Makefile version regardless of any package.json drift.
|
|
70
|
+
npm version "${VERSION}" --no-git-tag-version --allow-same-version >/dev/null 2>&1 || \
|
|
71
|
+
echo "::warning::could not stamp package.json to ${VERSION} (continuing)"
|
|
72
|
+
echo "Publishing version ${VERSION} (source: Makefile PROJECT_VERSION)"
|
|
88
73
|
|
|
89
|
-
- name:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
- name: Install & build
|
|
75
|
+
run: |
|
|
76
|
+
set -euo pipefail
|
|
77
|
+
npm ci --no-audit --no-fund || npm install --no-audit --no-fund
|
|
78
|
+
npm run build --if-present
|
|
94
79
|
|
|
95
|
-
- name:
|
|
96
|
-
if: steps.detect.outputs.changed == 'true'
|
|
80
|
+
- name: Publish to the MokoGIT npm registry (per-run Actions token)
|
|
97
81
|
env:
|
|
98
|
-
|
|
99
|
-
|
|
82
|
+
# The run's own Gitea Actions token; packages:write is granted above.
|
|
83
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
100
84
|
run: |
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
85
|
+
set -euo pipefail
|
|
86
|
+
OWNER='${{ github.repository_owner }}'
|
|
87
|
+
REG="https://${MOKOGIT_HOST}/api/packages/${OWNER}/npm/"
|
|
88
|
+
printf '//%s/api/packages/%s/npm/:_authToken=%s\n' "${MOKOGIT_HOST}" "${OWNER}" "${NODE_AUTH_TOKEN}" > .npmrc
|
|
89
|
+
# Capture output so we can tell a benign "already published" apart from a
|
|
90
|
+
# real failure (auth, network) — the latter MUST fail the run, not hide
|
|
91
|
+
# behind a warning. `set +e` so a non-zero exit doesn't kill the step
|
|
92
|
+
# before we classify it.
|
|
93
|
+
set +e
|
|
94
|
+
out="$(npm publish --registry "${REG}" 2>&1)"; rc=$?
|
|
95
|
+
set -e
|
|
96
|
+
rm -f .npmrc
|
|
97
|
+
echo "$out"
|
|
98
|
+
if [ "$rc" -eq 0 ]; then
|
|
99
|
+
echo "Published ${{ steps.v.outputs.version }} to the MokoGIT registry."
|
|
100
|
+
elif printf '%s' "$out" | grep -qiE 'cannot publish over|already exists|EPUBLISHCONFLICT|409 Conflict'; then
|
|
101
|
+
echo "::notice::MokoGIT registry already has ${{ steps.v.outputs.version }} — no-op (idempotent)."
|
|
108
102
|
else
|
|
109
|
-
echo
|
|
110
|
-
|
|
111
|
-
echo "::add-mask::${MOKO_CLONE_TOKEN}"
|
|
112
|
-
if [ -z "${MOKO_CLONE_TOKEN}" ]; then
|
|
113
|
-
echo "::error::MOKOGIT_TOKEN secret is empty — cannot clone mokocli fallback." >&2
|
|
114
|
-
exit 1
|
|
115
|
-
fi
|
|
116
|
-
if ! command -v composer > /dev/null 2>&1; then
|
|
117
|
-
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
|
118
|
-
fi
|
|
119
|
-
rm -rf /tmp/mokocli
|
|
120
|
-
CLONE_URL="https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git"
|
|
121
|
-
git clone --depth 1 --branch main --quiet "${CLONE_URL}" /tmp/mokocli
|
|
122
|
-
( cd /tmp/mokocli && composer install --no-dev --no-interaction --no-progress --optimize-autoloader --quiet )
|
|
123
|
-
echo "/tmp/mokocli/cli" >> "$GITHUB_PATH"
|
|
103
|
+
echo "::error::MokoGIT-registry publish FAILED (npm exit ${rc}) — this is NOT 'already published'. See the npm error above (e.g. E401 = the Actions token was rejected)."
|
|
104
|
+
exit "$rc"
|
|
124
105
|
fi
|
|
125
106
|
|
|
126
|
-
- name:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- name: Read version to publish
|
|
131
|
-
id: version
|
|
132
|
-
if: steps.detect.outputs.changed == 'true'
|
|
107
|
+
- name: Publish to npmjs.org (NPM_TOKEN, best-effort)
|
|
108
|
+
env:
|
|
109
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
133
110
|
run: |
|
|
134
111
|
set -euo pipefail
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
112
|
+
if [ -z "${NPM_TOKEN:-}" ]; then
|
|
113
|
+
echo "::notice::NPM_TOKEN not set — skipping the public npmjs.org publish (MokoGIT registry is the primary)."
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
printf '//registry.npmjs.org/:_authToken=%s\n' "${NPM_TOKEN}" > .npmrc
|
|
117
|
+
# npmjs.org is the best-effort secondary (MokoGIT registry is primary),
|
|
118
|
+
# so a real failure warns loudly but does not fail the run — yet we still
|
|
119
|
+
# distinguish "already published" (expected) from "token invalid" (needs
|
|
120
|
+
# attention) instead of collapsing both into one vague message.
|
|
121
|
+
set +e
|
|
122
|
+
out="$(npm publish --registry https://registry.npmjs.org/ --access public 2>&1)"; rc=$?
|
|
123
|
+
set -e
|
|
124
|
+
rm -f .npmrc
|
|
125
|
+
echo "$out"
|
|
126
|
+
if [ "$rc" -eq 0 ]; then
|
|
127
|
+
echo "Published ${{ steps.v.outputs.version }} to npmjs.org."
|
|
128
|
+
elif printf '%s' "$out" | grep -qiE 'cannot publish over|already exists|EPUBLISHCONFLICT|409 Conflict'; then
|
|
129
|
+
echo "::notice::npmjs.org already has ${{ steps.v.outputs.version }} — no-op."
|
|
130
|
+
else
|
|
131
|
+
echo "::warning::npmjs.org publish failed (npm exit ${rc}) — token likely invalid/expired. Secondary registry only; not failing the run. See npm error above."
|
|
132
|
+
fi
|
|
142
133
|
|
|
143
|
-
- name:
|
|
144
|
-
if:
|
|
134
|
+
- name: Summary
|
|
135
|
+
if: always()
|
|
145
136
|
run: |
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
153
|
-
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
137
|
+
{
|
|
138
|
+
echo "## npm publish — ${{ steps.v.outputs.version }}"
|
|
139
|
+
echo "- Version source: root \`Makefile\` PROJECT_VERSION"
|
|
140
|
+
echo "- MokoGIT registry: per-run Actions token (\`packages: write\`)"
|
|
141
|
+
echo "- npmjs.org: \`NPM_TOKEN\` (best-effort)"
|
|
142
|
+
} >> "$GITHUB_STEP_SUMMARY"
|