fix: force-refresh moko-platform clone + delete legacy templates/workflows/
- Add rm -rf before git clone in all workflow setup steps to prevent stale CLI tools from being reused across runner jobs - Delete templates/workflows/ — .mokogitea/workflows/ is the canonical source for workflow templates Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,116 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/dependency-audit.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Scheduled dependency audit — runs composer audit across repos
|
||||
|
||||
name: Dependency Audit
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * 1' # Every Monday at 08:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
tools: composer
|
||||
|
||||
- name: Run composer audit
|
||||
id: audit
|
||||
run: |
|
||||
if [ ! -f composer.json ]; then
|
||||
echo "No composer.json found — skipping."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
set +e
|
||||
AUDIT_OUTPUT=$(composer audit --format=json 2>&1)
|
||||
AUDIT_EXIT=$?
|
||||
set -e
|
||||
|
||||
echo "$AUDIT_OUTPUT" > audit-results.json
|
||||
|
||||
if [ "$AUDIT_EXIT" -ne 0 ]; then
|
||||
echo "vulnerable=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "vulnerable=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Parse vulnerabilities
|
||||
if: steps.audit.outputs.skip != 'true'
|
||||
id: parse
|
||||
run: |
|
||||
if [ "${{ steps.audit.outputs.vulnerable }}" = "true" ]; then
|
||||
echo "## Vulnerabilities Found" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# Extract advisory count
|
||||
ADVISORIES=$(jq -r '.advisories | length // 0' audit-results.json 2>/dev/null || echo "0")
|
||||
echo "Found **${ADVISORIES}** advisories." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# List each advisory
|
||||
jq -r '
|
||||
.advisories | to_entries[] |
|
||||
"| \(.key) | \(.value[0].title // "N/A") | \(.value[0].cve // "N/A") | \(.value[0].affectedVersions // "N/A") |"
|
||||
' audit-results.json 2>/dev/null | {
|
||||
echo "| Package | Title | CVE | Affected Versions |"
|
||||
echo "|---------|-------|-----|-------------------|"
|
||||
cat
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "count=${ADVISORIES}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "## No Vulnerabilities Found" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "All dependencies passed the audit." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "count=0" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Notify via ntfy
|
||||
if: steps.audit.outputs.vulnerable == 'true'
|
||||
run: |
|
||||
NTFY_URL="${{ vars.NTFY_URL }}"
|
||||
NTFY_TOPIC="${{ vars.NTFY_TOPIC }}"
|
||||
REPO="${{ github.repository }}"
|
||||
COUNT="${{ steps.parse.outputs.count }}"
|
||||
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
curl -s \
|
||||
-H "Title: Dependency Audit: ${REPO}" \
|
||||
-H "Priority: high" \
|
||||
-H "Tags: warning,package" \
|
||||
-H "Click: ${RUN_URL}" \
|
||||
-d "Found ${COUNT} vulnerability advisory(ies) in ${REPO}. Review the workflow run for details." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
||||
|
||||
- name: Summary
|
||||
if: always() && steps.audit.outputs.skip != 'true'
|
||||
run: |
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "---" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Audit completed at $(date -u '+%Y-%m-%d %H:%M:%S UTC')." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Repository: **${{ github.repository }}**" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Branch: **${{ github.ref_name }}**" >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -1,223 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Deploy
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/deploy-module.yml
|
||||
# VERSION: 02.00.00
|
||||
# BRIEF: Deploy Dolibarr module to dev, demo, or live environments
|
||||
#
|
||||
# Secrets required:
|
||||
# GA_TOKEN - Gitea API token for repo access
|
||||
# DEPLOY_SSH_KEY - SSH private key for server access
|
||||
# LIVE_TARGETS - JSON array of live instances (optional), e.g.:
|
||||
# [{"host":"client1.example.com","user":"deploy",
|
||||
# "mods_dir":"/path/MokoDoliMods",
|
||||
# "custom_dir":"/path/htdocs/custom"}]
|
||||
#
|
||||
# Variables required:
|
||||
# DEV_HOST, DEV_USER, DEV_MODS_DIR, DEV_CUSTOM_DIR
|
||||
# DEMO_HOST, DEMO_USER, DEMO_MODS_DIR, DEMO_CUSTOM_DIR
|
||||
|
||||
name: "Dolibarr: Deploy Module"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
module_repo:
|
||||
description: 'Module repo name (e.g. MokoCRM, MokoDoliSign)'
|
||||
required: true
|
||||
server:
|
||||
description: 'Target environment'
|
||||
required: true
|
||||
default: 'dev'
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- demo
|
||||
- live
|
||||
- dev+demo
|
||||
- all
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
ORG: ${{ vars.GITEA_ORG || 'MokoConsulting' }}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy ${{ inputs.module_repo }} to ${{ inputs.server }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Validate module repo
|
||||
run: |
|
||||
REPO="${{ inputs.module_repo }}"
|
||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||
"${GITEA_URL}/api/v1/repos/${ORG}/${REPO}")
|
||||
if [ "$STATUS" != "200" ]; then
|
||||
echo "::error::${ORG}/${REPO} not found (HTTP ${STATUS})"
|
||||
exit 1
|
||||
fi
|
||||
echo "REPO=${REPO}" >> $GITHUB_ENV
|
||||
|
||||
LINK_NAME=$(echo "$REPO" | sed 's/MokoDoli//;s/Moko//' | tr '[:upper:]' '[:lower:]')
|
||||
[ "$REPO" = "MokoCRM" ] && LINK_NAME="mokocrm"
|
||||
[ "$REPO" = "MokoDoliProjTemplate" ] && LINK_NAME="mokoprojtemplate"
|
||||
echo "LINK_NAME=${LINK_NAME}" >> $GITHUB_ENV
|
||||
|
||||
- name: Get latest stable tag
|
||||
run: |
|
||||
TAGS=$(curl -s \
|
||||
-H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||
"${GITEA_URL}/api/v1/repos/${ORG}/${{ env.REPO }}/tags?limit=1")
|
||||
TAG=$(echo "$TAGS" | jq -r '.[0].name // empty')
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
[ -n "$TAG" ] && echo "Deploying: $TAG" || echo "No tags - deploying main"
|
||||
|
||||
- name: Deploy to dev
|
||||
if: inputs.server == 'dev' || inputs.server == 'dev+demo' || inputs.server == 'all'
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ vars.DEV_HOST }}
|
||||
username: ${{ vars.DEV_USER }}
|
||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
script: |
|
||||
REPO="${{ env.REPO }}"
|
||||
LINK="${{ env.LINK_NAME }}"
|
||||
TAG="${{ env.TAG }}"
|
||||
MODS="${{ vars.DEV_MODS_DIR }}"
|
||||
CUSTOM="${{ vars.DEV_CUSTOM_DIR }}"
|
||||
|
||||
mkdir -p "$MODS" && cd "$MODS"
|
||||
if [ -d "$REPO" ]; then
|
||||
cd "$REPO" && git fetch --tags origin
|
||||
else
|
||||
git clone "${{ env.GITEA_URL }}/${{ env.ORG }}/${REPO}.git"
|
||||
cd "$REPO"
|
||||
fi
|
||||
|
||||
if [ -n "$TAG" ]; then
|
||||
git checkout "$TAG" --quiet
|
||||
else
|
||||
git checkout main --quiet
|
||||
git pull --ff-only origin main --quiet
|
||||
fi
|
||||
|
||||
cd "$CUSTOM"
|
||||
[ -L "$LINK" ] || [ -d "$LINK" ] && rm -rf "$LINK"
|
||||
ln -sf "$MODS/$REPO/src" "$LINK"
|
||||
echo "OK: $LINK -> $MODS/$REPO/src (${TAG:-main})"
|
||||
|
||||
- name: Deploy to demo
|
||||
if: inputs.server == 'demo' || inputs.server == 'dev+demo' || inputs.server == 'all'
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ vars.DEMO_HOST }}
|
||||
username: ${{ vars.DEMO_USER }}
|
||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
script: |
|
||||
REPO="${{ env.REPO }}"
|
||||
LINK="${{ env.LINK_NAME }}"
|
||||
TAG="${{ env.TAG }}"
|
||||
MODS="${{ vars.DEMO_MODS_DIR }}"
|
||||
CUSTOM="${{ vars.DEMO_CUSTOM_DIR }}"
|
||||
|
||||
mkdir -p "$MODS" && cd "$MODS"
|
||||
if [ -d "$REPO" ]; then
|
||||
cd "$REPO" && git fetch --tags origin
|
||||
else
|
||||
git clone "${{ env.GITEA_URL }}/${{ env.ORG }}/${REPO}.git"
|
||||
cd "$REPO"
|
||||
fi
|
||||
|
||||
if [ -n "$TAG" ]; then
|
||||
git checkout "$TAG" --quiet
|
||||
else
|
||||
git checkout main --quiet
|
||||
git pull --ff-only origin main --quiet
|
||||
fi
|
||||
|
||||
cd "$CUSTOM"
|
||||
[ -L "$LINK" ] || [ -d "$LINK" ] && rm -rf "$LINK"
|
||||
ln -sf "$MODS/$REPO/src" "$LINK"
|
||||
echo "OK: $LINK -> $MODS/$REPO/src (${TAG:-main})"
|
||||
|
||||
- name: Deploy to live
|
||||
if: inputs.server == 'live' || inputs.server == 'all'
|
||||
env:
|
||||
LIVE_TARGETS: ${{ secrets.LIVE_TARGETS }}
|
||||
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
run: |
|
||||
if [ -z "$LIVE_TARGETS" ] || [ "$LIVE_TARGETS" = "null" ]; then
|
||||
echo "::error::LIVE_TARGETS secret is not configured."
|
||||
echo "Set it to a JSON array of target objects."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COUNT=$(echo "$LIVE_TARGETS" | jq 'length')
|
||||
echo "Deploying to ${COUNT} live instance(s)..."
|
||||
|
||||
echo "$DEPLOY_KEY" > /tmp/deploy_key
|
||||
chmod 600 /tmp/deploy_key
|
||||
|
||||
FAILED=0
|
||||
for i in $(seq 0 $((COUNT - 1))); do
|
||||
HOST=$(echo "$LIVE_TARGETS" | jq -r ".[$i].host")
|
||||
USER=$(echo "$LIVE_TARGETS" | jq -r ".[$i].user")
|
||||
MODS=$(echo "$LIVE_TARGETS" | jq -r ".[$i].mods_dir")
|
||||
CUSTOM=$(echo "$LIVE_TARGETS" | jq -r ".[$i].custom_dir")
|
||||
PORT=$(echo "$LIVE_TARGETS" | jq -r ".[$i].port // 22")
|
||||
LABEL=$(echo "$LIVE_TARGETS" | jq -r ".[$i].label // empty")
|
||||
[ -z "$LABEL" ] && LABEL="$HOST"
|
||||
|
||||
echo ""
|
||||
echo "=== Instance $((i+1))/${COUNT}: ${LABEL} (${USER}@${HOST}:${PORT}) ==="
|
||||
|
||||
ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 \
|
||||
-i /tmp/deploy_key -p "$PORT" "${USER}@${HOST}" \
|
||||
"REPO='${{ env.REPO }}' LINK='${{ env.LINK_NAME }}' TAG='${{ env.TAG }}' MODS='${MODS}' CUSTOM='${CUSTOM}' GITEA_URL='${{ env.GITEA_URL }}' ORG='${{ env.ORG }}' bash" <<'REMOTE_SCRIPT' || { echo "::warning::Failed: ${LABEL}"; FAILED=$((FAILED+1)); continue; }
|
||||
mkdir -p "$MODS" && cd "$MODS"
|
||||
if [ -d "$REPO" ]; then
|
||||
cd "$REPO" && git fetch --tags origin
|
||||
else
|
||||
git clone "${GITEA_URL}/${ORG}/${REPO}.git"
|
||||
cd "$REPO"
|
||||
fi
|
||||
|
||||
if [ -n "$TAG" ]; then
|
||||
git checkout "$TAG" --quiet
|
||||
else
|
||||
git checkout main --quiet
|
||||
git pull --ff-only origin main --quiet
|
||||
fi
|
||||
|
||||
cd "$CUSTOM"
|
||||
[ -L "$LINK" ] || [ -d "$LINK" ] && rm -rf "$LINK"
|
||||
ln -sf "$MODS/$REPO/src" "$LINK"
|
||||
echo "OK: $LINK -> $MODS/$REPO/src (${TAG:-main})"
|
||||
REMOTE_SCRIPT
|
||||
done
|
||||
|
||||
rm -f /tmp/deploy_key
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
echo "::error::${FAILED} of ${COUNT} live deployment(s) failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "All ${COUNT} live instance(s) deployed successfully."
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Deploy: ${{ env.REPO }} -> ${{ inputs.server }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Module | \`${{ env.REPO }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Symlink | \`${{ env.LINK_NAME }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${{ env.TAG || 'main' }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Target | \`${{ inputs.server }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,95 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Security
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||
# PATH: /templates/workflows/gitleaks.yml.template
|
||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||
#
|
||||
# +========================================================================+
|
||||
# | SECRET SCANNING |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Scans commits for leaked secrets using Gitleaks. |
|
||||
# | |
|
||||
# | - PR scan: only new commits in the PR |
|
||||
# | - Scheduled: full repo scan weekly |
|
||||
# | - Alerts via ntfy on findings |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: Secret Scanning
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'dev/**'
|
||||
schedule:
|
||||
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Gitleaks Secret Scan
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Gitleaks
|
||||
run: |
|
||||
GITLEAKS_VERSION="8.21.2"
|
||||
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||
| tar -xz -C /usr/local/bin gitleaks
|
||||
gitleaks version
|
||||
|
||||
- name: Scan for secrets
|
||||
id: scan
|
||||
run: |
|
||||
echo "### Secret Scanning" >> $GITHUB_STEP_SUMMARY
|
||||
ARGS="--source . --verbose --report-format json --report-path /tmp/gitleaks-report.json"
|
||||
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
# Scan only PR commits
|
||||
ARGS="$ARGS --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
|
||||
echo "Scanning PR commits only" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Full repository scan" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if gitleaks detect $ARGS 2>&1; then
|
||||
echo "result=clean" >> "$GITHUB_OUTPUT"
|
||||
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "result=found" >> "$GITHUB_OUTPUT"
|
||||
FINDINGS=$(jq length /tmp/gitleaks-report.json 2>/dev/null || echo "unknown")
|
||||
echo "**${FINDINGS} potential secret(s) detected.**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Review the findings and rotate any exposed credentials immediately." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Notify on findings
|
||||
if: failure() && steps.scan.outputs.result == 'found'
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
curl -sS \
|
||||
-H "Title: ${REPO} — secrets detected in code" \
|
||||
-H "Tags: rotating_light,key" \
|
||||
-H "Priority: urgent" \
|
||||
-d "Gitleaks found potential secrets. Review and rotate credentials immediately." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||
@@ -1,278 +0,0 @@
|
||||
# MCP Server Auto-Release
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# MCP-specific release pipeline that builds TypeScript, runs validation,
|
||||
# attaches the compiled dist/ as a release artifact, and creates a GitHub
|
||||
# Release with tool inventory in the release notes.
|
||||
#
|
||||
# This replaces the generic auto-release.yml for MCP server repos.
|
||||
|
||||
name: MCP Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
- 'tsconfig.json'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
name: Build, Validate & Release
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
github.actor != 'github-actions[bot]'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN || github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
# ── Build ────────────────────────────────────────────────────────
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: TypeScript compile check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Verify dist output
|
||||
run: |
|
||||
for f in index.js client.js config.js types.js; do
|
||||
test -f "dist/${f}" || (echo "ERROR: dist/${f} not found" && exit 1)
|
||||
done
|
||||
echo "✓ All dist files present"
|
||||
|
||||
# ── Tool Inventory ───────────────────────────────────────────────
|
||||
- name: Generate tool inventory
|
||||
id: tools
|
||||
run: |
|
||||
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || echo "0")
|
||||
echo "count=${TOOL_COUNT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Extract tool names
|
||||
TOOL_LIST=$(grep -oE "'[a-z_]+'" src/index.ts | head -100 | tr -d "'" | sort -u)
|
||||
echo "Tools registered: ${TOOL_COUNT}"
|
||||
|
||||
# Generate inventory for release notes
|
||||
echo "## Tool Inventory (${TOOL_COUNT} tools)" > /tmp/tool-inventory.md
|
||||
echo "" >> /tmp/tool-inventory.md
|
||||
grep -B0 -A1 "server\.tool(" src/index.ts | grep -oE "'[^']+'" | while IFS= read -r name; do
|
||||
read -r desc 2>/dev/null || true
|
||||
CLEAN_NAME=$(echo "$name" | tr -d "'")
|
||||
CLEAN_DESC=$(echo "$desc" | tr -d "'" | sed 's/,$//')
|
||||
if [ -n "$CLEAN_NAME" ] && [ -n "$CLEAN_DESC" ]; then
|
||||
echo "- \`${CLEAN_NAME}\` — ${CLEAN_DESC}" >> /tmp/tool-inventory.md
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Version ──────────────────────────────────────────────────────
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch version/04 --quiet \
|
||||
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
||||
/tmp/mokostandards
|
||||
cd /tmp/mokostandards
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
- name: Read version from README.md
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "No VERSION in README.md — skipping release"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
|
||||
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
||||
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$PATCH" = "00" ]; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$PATCH" = "01" ]; then
|
||||
echo "is_first=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_first=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Check if already released
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
id: check
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.release_tag }}"
|
||||
TAG_EXISTS=false
|
||||
git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true
|
||||
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ── Release Artifact ─────────────────────────────────────────────
|
||||
- name: Package dist
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
tar -czf "/tmp/${REPO_NAME}-${VERSION}.tar.gz" -C dist .
|
||||
echo "artifact=/tmp/${REPO_NAME}-${VERSION}.tar.gz" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ── Version Updates ──────────────────────────────────────────────
|
||||
- name: Set platform version
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
php /tmp/mokostandards/api/cli/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch main
|
||||
|
||||
- name: Update version badges
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do
|
||||
if grep -q '\[VERSION:' "$f" 2>/dev/null; then
|
||||
sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Commit release changes
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true'
|
||||
run: |
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add -A
|
||||
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
||||
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
||||
git push
|
||||
|
||||
# ── Version Branch ───────────────────────────────────────────────
|
||||
- name: Archive version branch
|
||||
if: steps.check.outputs.tag_exists != 'true'
|
||||
run: |
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
git push origin HEAD:"$BRANCH" --force
|
||||
echo "Updated archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Tag & Release ────────────────────────────────────────────────
|
||||
- name: Create git tag
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true' &&
|
||||
steps.version.outputs.is_first == 'true'
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.release_tag }}"
|
||||
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
git tag "$TAG"
|
||||
git push origin "$TAG"
|
||||
fi
|
||||
|
||||
- name: GitHub Release
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
MAJOR="${{ steps.version.outputs.major }}"
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
TOOL_COUNT="${{ steps.tools.outputs.count }}"
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
|
||||
# Build release notes
|
||||
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
|
||||
{
|
||||
echo "$NOTES"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "### MCP Server Info"
|
||||
echo "- **Tools registered**: ${TOOL_COUNT}"
|
||||
echo "- **Node.js**: 20+"
|
||||
echo "- **MCP SDK**: $(node -p \"require('./package.json').dependencies['@modelcontextprotocol/sdk']\" 2>/dev/null || echo 'unknown')"
|
||||
echo ""
|
||||
cat /tmp/tool-inventory.md 2>/dev/null || true
|
||||
} > /tmp/release_notes.md
|
||||
|
||||
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||
|
||||
ARTIFACT="/tmp/${REPO_NAME}-${VERSION}.tar.gz"
|
||||
|
||||
if [ -z "$EXISTING" ]; then
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
--notes-file /tmp/release_notes.md \
|
||||
--target "$BRANCH" \
|
||||
"$ARTIFACT"
|
||||
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
--notes-file /tmp/release_notes.md
|
||||
gh release upload "$RELEASE_TAG" "$ARTIFACT" --clobber 2>/dev/null || true
|
||||
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
TOOL_COUNT="${{ steps.tools.outputs.count }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## MCP Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Detail | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tools | ${TOOL_COUNT} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.release_tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -1,61 +0,0 @@
|
||||
# MCP Server Build & Validation
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Builds the MCP server, validates TypeScript compilation, and checks
|
||||
# that tools are properly registered with valid Zod schemas.
|
||||
|
||||
name: MCP Build & Validate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev/**]
|
||||
paths: ['src/**', 'package.json', 'tsconfig.json']
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths: ['src/**', 'package.json', 'tsconfig.json']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: TypeScript compile
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f dist/index.js || (echo "ERROR: dist/index.js not found" && exit 1)
|
||||
test -f dist/client.js || (echo "ERROR: dist/client.js not found" && exit 1)
|
||||
test -f dist/config.js || (echo "ERROR: dist/config.js not found" && exit 1)
|
||||
test -f dist/types.js || (echo "ERROR: dist/types.js not found" && exit 1)
|
||||
echo "✓ All required dist files present"
|
||||
|
||||
- name: Verify shebang in index.js
|
||||
run: |
|
||||
head -1 dist/index.js | grep -q "#!/usr/bin/env node" || echo "WARNING: Missing shebang in dist/index.js"
|
||||
|
||||
- name: Count registered tools
|
||||
run: |
|
||||
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || true)
|
||||
echo "Registered tools: ${TOOL_COUNT}"
|
||||
if [ "${TOOL_COUNT}" -eq 0 ]; then
|
||||
echo "ERROR: No tools registered in src/index.ts"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,105 +0,0 @@
|
||||
# MCP SDK Version Check
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Weekly check for MCP SDK updates. Creates an issue when a new version
|
||||
# of @modelcontextprotocol/sdk is available.
|
||||
|
||||
name: MCP SDK Version Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * 1' # Every Monday at 9am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-sdk:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Check for SDK updates
|
||||
id: sdk-check
|
||||
run: |
|
||||
CURRENT=$(node -p "require('./package.json').dependencies['@modelcontextprotocol/sdk']" | sed 's/[\^~]//')
|
||||
LATEST=$(npm view @modelcontextprotocol/sdk version 2>/dev/null || echo "unknown")
|
||||
|
||||
echo "current=${CURRENT}" >> $GITHUB_OUTPUT
|
||||
echo "latest=${LATEST}" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "${CURRENT}" != "${LATEST}" ] && [ "${LATEST}" != "unknown" ]; then
|
||||
echo "update_available=true" >> $GITHUB_OUTPUT
|
||||
echo "MCP SDK update available: ${CURRENT} → ${LATEST}"
|
||||
else
|
||||
echo "update_available=false" >> $GITHUB_OUTPUT
|
||||
echo "MCP SDK is up to date: ${CURRENT}"
|
||||
fi
|
||||
|
||||
- name: Check for Zod updates
|
||||
id: zod-check
|
||||
run: |
|
||||
CURRENT=$(node -p "require('./package.json').dependencies['zod']" | sed 's/[\^~]//')
|
||||
LATEST=$(npm view zod version 2>/dev/null || echo "unknown")
|
||||
|
||||
echo "current=${CURRENT}" >> $GITHUB_OUTPUT
|
||||
echo "latest=${LATEST}" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "${CURRENT}" != "${LATEST}" ] && [ "${LATEST}" != "unknown" ]; then
|
||||
echo "update_available=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "update_available=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create update issue
|
||||
if: steps.sdk-check.outputs.update_available == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const title = `chore(deps): update @modelcontextprotocol/sdk ${process.env.CURRENT} → ${process.env.LATEST}`;
|
||||
const body = [
|
||||
'## MCP SDK Update Available',
|
||||
'',
|
||||
`| Package | Current | Latest |`,
|
||||
`|---------|---------|--------|`,
|
||||
`| @modelcontextprotocol/sdk | ${process.env.CURRENT} | ${process.env.LATEST} |`,
|
||||
`| zod | ${process.env.ZOD_CURRENT} | ${process.env.ZOD_LATEST} |`,
|
||||
'',
|
||||
'### Steps',
|
||||
'1. Update package.json',
|
||||
'2. Run `npm install`',
|
||||
'3. Run `npm run build` to verify compilation',
|
||||
'4. Test all tools against target API',
|
||||
'',
|
||||
'### Changelog',
|
||||
`https://github.com/modelcontextprotocol/typescript-sdk/releases`,
|
||||
].join('\n');
|
||||
|
||||
// Check for existing open issue
|
||||
const existing = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
labels: 'api-change',
|
||||
});
|
||||
|
||||
const alreadyExists = existing.data.some(i => i.title.includes('@modelcontextprotocol/sdk'));
|
||||
if (!alreadyExists) {
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title,
|
||||
body,
|
||||
labels: ['api-change', 'chore'],
|
||||
});
|
||||
}
|
||||
env:
|
||||
CURRENT: ${{ steps.sdk-check.outputs.current }}
|
||||
LATEST: ${{ steps.sdk-check.outputs.latest }}
|
||||
ZOD_CURRENT: ${{ steps.zod-check.outputs.current }}
|
||||
ZOD_LATEST: ${{ steps.zod-check.outputs.latest }}
|
||||
@@ -1,57 +0,0 @@
|
||||
# MCP Tool Inventory
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Generates a tool inventory report on each push to main.
|
||||
# Extracts tool names, descriptions, and parameter counts from src/index.ts.
|
||||
|
||||
name: MCP Tool Inventory
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['src/index.ts']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
inventory:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Generate tool inventory
|
||||
run: |
|
||||
echo "# MCP Tool Inventory" > TOOLS.md
|
||||
echo "" >> TOOLS.md
|
||||
echo "Auto-generated from \`src/index.ts\` on $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> TOOLS.md
|
||||
echo "" >> TOOLS.md
|
||||
|
||||
# Count tools
|
||||
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || true)
|
||||
echo "**Total tools: ${TOOL_COUNT}**" >> TOOLS.md
|
||||
echo "" >> TOOLS.md
|
||||
|
||||
# Extract tool names and descriptions
|
||||
echo "| Tool | Description |" >> TOOLS.md
|
||||
echo "|------|-------------|" >> TOOLS.md
|
||||
|
||||
grep -A1 "server\.tool(" src/index.ts | grep -E "^\s*'" | while read -r line; do
|
||||
TOOL_NAME=$(echo "$line" | sed "s/.*'\([^']*\)'.*/\1/")
|
||||
# Get next line for description
|
||||
DESC=$(grep -A2 "'${TOOL_NAME}'" src/index.ts | grep -E "^\s*'" | tail -1 | sed "s/.*'\([^']*\)'.*/\1/" || echo "")
|
||||
echo "| \`${TOOL_NAME}\` | ${DESC} |" >> TOOLS.md
|
||||
done
|
||||
|
||||
echo "" >> TOOLS.md
|
||||
echo "---" >> TOOLS.md
|
||||
echo "*Generated by MCP Tool Inventory workflow*" >> TOOLS.md
|
||||
|
||||
cat TOOLS.md
|
||||
|
||||
- name: Upload inventory artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tool-inventory
|
||||
path: TOOLS.md
|
||||
retention-days: 90
|
||||
Reference in New Issue
Block a user