@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
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
# INGROUP: MokoCLI.Release
|
|
8
8
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
|
9
9
|
# PATH: /.mokogit/workflows/pre-release.yml
|
|
10
|
-
# VERSION: 06.
|
|
11
|
-
# BRIEF: Batched pre-release on nightly schedule + manual dispatch (rolling dev/rc asset, build-metadata version — #404); non-Joomla MokoGIT release + cosmetic release-notes are best-effort. npm publishing moved OUT of this universal workflow into Template-NPM's dedicated npm-publish.yml so it can't leak into non-npm repos via sync.
|
|
10
|
+
# VERSION: 06.03.00
|
|
11
|
+
# BRIEF: Batched pre-release on nightly schedule + manual dispatch (rolling dev/rc asset, build-metadata version — #404); non-Joomla MokoGIT release + cosmetic release-notes are best-effort. npm publishing moved OUT of this universal workflow into Template-NPM's dedicated npm-publish.yml so it can't leak into non-npm repos via sync. The dev push trigger now skips [skip ci]/[skip bump] commits (bump-on-dev: release_bump_dev.php's [skip ci] base-bump commit on dev must not spawn a redundant dev-channel build).
|
|
12
12
|
|
|
13
13
|
name: "Universal: Pre-Release"
|
|
14
14
|
|
|
@@ -93,7 +93,11 @@ jobs:
|
|
|
93
93
|
if: >-
|
|
94
94
|
!startsWith(github.event.repository.name, 'Template-') &&
|
|
95
95
|
(
|
|
96
|
-
|
|
96
|
+
(
|
|
97
|
+
github.event_name == 'push' &&
|
|
98
|
+
!contains(github.event.head_commit.message, '[skip ci]') &&
|
|
99
|
+
!contains(github.event.head_commit.message, '[skip bump]')
|
|
100
|
+
) ||
|
|
97
101
|
github.event_name == 'schedule' ||
|
|
98
102
|
(
|
|
99
103
|
github.event_name == 'workflow_dispatch' &&
|
|
@@ -305,6 +309,19 @@ jobs:
|
|
|
305
309
|
echo "::notice::Platform '$PLATFORM' eligible for pre-release (joomla_family=${IS_JOOMLA_FAMILY})"
|
|
306
310
|
fi
|
|
307
311
|
|
|
312
|
+
- name: "Require Makefile (pre-release gate)"
|
|
313
|
+
if: steps.eligibility.outputs.proceed == 'true'
|
|
314
|
+
# Enforce the same Makefile requirement as auto-release, but only when the
|
|
315
|
+
# platform is eligible to build/release (pre-release legitimately no-ops for
|
|
316
|
+
# ineligible platforms). Post-checkout step: a job-level if: cannot see the workspace.
|
|
317
|
+
run: |
|
|
318
|
+
if [ ! -f Makefile ] && [ ! -f makefile ] && [ ! -f GNUmakefile ]; then
|
|
319
|
+
echo "::error::pre-release requires a Makefile at the repository root — none found. A release-bearing repo must ship a Makefile (build/package targets). Aborting."
|
|
320
|
+
echo "## Pre-release blocked: no Makefile at repo root" >> "$GITHUB_STEP_SUMMARY"
|
|
321
|
+
exit 1
|
|
322
|
+
fi
|
|
323
|
+
echo "Makefile present — pre-release may proceed."
|
|
324
|
+
|
|
308
325
|
- name: Resolve metadata and bump version
|
|
309
326
|
id: meta
|
|
310
327
|
if: steps.eligibility.outputs.proceed == 'true'
|
|
@@ -16,6 +16,9 @@ on:
|
|
|
16
16
|
pull_request:
|
|
17
17
|
types: [closed]
|
|
18
18
|
|
|
19
|
+
permissions:
|
|
20
|
+
contents: write
|
|
21
|
+
|
|
19
22
|
env:
|
|
20
23
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
21
24
|
|
|
@@ -34,7 +37,7 @@ jobs:
|
|
|
34
37
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
|
35
38
|
REPO: ${{ github.repository }}
|
|
36
39
|
GIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
37
|
-
TOKEN: ${{
|
|
40
|
+
TOKEN: ${{ github.token }}
|
|
38
41
|
run: |
|
|
39
42
|
set -euo pipefail
|
|
40
43
|
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,23 +4,6 @@ All notable changes to this project are documented here, following [Keep a Chang
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
## [01.01.00] - 2026-08-15
|
|
8
|
-
|
|
9
|
-
### Added
|
|
10
|
-
- **Layer 1 (Extensions Store / MokoSuiteExtensions)** — 5 MCP tools against the
|
|
11
|
-
`/v1/mokosuiteextensions` base path:
|
|
12
|
-
- Reads: `store_catalog_list` (`GET /catalog`), `store_catalog_get`
|
|
13
|
-
(`GET /catalog/{id}`).
|
|
14
|
-
- Writes: `store_install` (`POST /catalog/{id}/install`), `store_update`
|
|
15
|
-
(`POST /catalog/{id}/update`), `store_uninstall`
|
|
16
|
-
(`POST /catalog/{id}/uninstall`, `confirm: true` guard — destructive).
|
|
17
|
-
- Bumped the MCP server advertised version to `1.1.0`.
|
|
18
|
-
|
|
19
|
-
### Note
|
|
20
|
-
- The store write routes (`/install`, `/update`, `/uninstall`) are being added to
|
|
21
|
-
the store REST API in parallel and may not be live on a site until that ships and
|
|
22
|
-
deploys. The read routes exist. The tools are defined now regardless.
|
|
23
|
-
|
|
24
7
|
## [01.00.00] - 2026-07-26
|
|
25
8
|
|
|
26
9
|
### Added
|
package/Makefile
CHANGED
|
@@ -1,70 +1,94 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NPM / Node Project Makefile
|
|
2
2
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
#
|
|
5
|
+
# Native build/test/lint/package logic for an npm package (incl. MCP servers). The
|
|
6
|
+
# identity block below is the version source of truth — auto-filled per repo by the
|
|
7
|
+
# mokocli workflow sync (--makefiles).
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
# ==============================================================================
|
|
10
|
+
# Identity (auto-filled by workflow sync — the Makefile is the version source of truth)
|
|
11
|
+
# ==============================================================================
|
|
12
|
+
PROJECT_NAME := mcp-mokosuite
|
|
13
|
+
PROJECT_VERSION := 1.1.0
|
|
14
|
+
PLATFORM := npm
|
|
7
15
|
|
|
8
|
-
|
|
16
|
+
# ==============================================================================
|
|
17
|
+
# Configuration
|
|
18
|
+
# ==============================================================================
|
|
19
|
+
SRC_DIR := src/
|
|
20
|
+
BUILD_DIR := build
|
|
21
|
+
NPM := npm
|
|
22
|
+
NODE := node
|
|
23
|
+
# Prefer `npm ci` when a lockfile is present (reproducible), else `npm install`.
|
|
24
|
+
NPM_INSTALL := $(shell [ -f package-lock.json ] && echo ci || echo install)
|
|
9
25
|
|
|
10
|
-
COLOR_RESET
|
|
11
|
-
COLOR_GREEN
|
|
12
|
-
|
|
26
|
+
COLOR_RESET := \033[0m
|
|
27
|
+
COLOR_GREEN := \033[32m
|
|
28
|
+
COLOR_YELLOW := \033[33m
|
|
29
|
+
COLOR_BLUE := \033[34m
|
|
30
|
+
COLOR_RED := \033[31m
|
|
13
31
|
|
|
32
|
+
# ==============================================================================
|
|
33
|
+
# Targets
|
|
34
|
+
# ==============================================================================
|
|
14
35
|
.PHONY: help
|
|
15
36
|
help: ## Show this help message
|
|
16
|
-
@echo "$(COLOR_BLUE)$(PROJECT_NAME) v$(PROJECT_VERSION)$(COLOR_RESET)"
|
|
17
|
-
@echo ""
|
|
18
|
-
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
|
|
37
|
+
@echo "$(COLOR_BLUE)$(PROJECT_NAME) v$(PROJECT_VERSION) — npm package$(COLOR_RESET)"
|
|
19
38
|
@echo ""
|
|
39
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-16s$(COLOR_RESET) %s\n", $$1, $$2}'
|
|
20
40
|
|
|
21
41
|
.PHONY: install-deps
|
|
22
|
-
install-deps: ## Install dependencies
|
|
23
|
-
@$(NPM)
|
|
24
|
-
@echo "$(COLOR_GREEN)✓
|
|
25
|
-
|
|
26
|
-
MOKO_PLATFORM ?= $(or $(wildcard ../mokocli),$(wildcard $(HOME)/mokocli),$(wildcard /opt/mokocli))
|
|
27
|
-
MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js
|
|
42
|
+
install-deps: ## Install dependencies (npm ci if lockfile present, else install)
|
|
43
|
+
@$(NPM) $(NPM_INSTALL)
|
|
44
|
+
@echo "$(COLOR_GREEN)✓ dependencies installed$(COLOR_RESET)"
|
|
28
45
|
|
|
29
|
-
.PHONY:
|
|
30
|
-
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
node "$(MINIFY_SCRIPT)" $(SRC_DIR); \
|
|
34
|
-
elif [ -f "scripts/minify.js" ]; then \
|
|
35
|
-
node scripts/minify.js; \
|
|
36
|
-
else \
|
|
37
|
-
echo "No minify script found"; \
|
|
38
|
-
fi
|
|
46
|
+
.PHONY: lint
|
|
47
|
+
lint: ## Run the package lint script when defined
|
|
48
|
+
@if grep -q '"lint"' package.json 2>/dev/null; then $(NPM) run lint; \
|
|
49
|
+
else echo "$(COLOR_YELLOW)⚠ no lint script — skipping$(COLOR_RESET)"; fi
|
|
39
50
|
|
|
40
|
-
.PHONY:
|
|
41
|
-
|
|
42
|
-
@$(NPM) run build
|
|
43
|
-
@echo "$(COLOR_GREEN)✓ Build complete$(COLOR_RESET)"
|
|
51
|
+
.PHONY: validate
|
|
52
|
+
validate: lint ## Alias: static validation (lint)
|
|
44
53
|
|
|
45
|
-
.PHONY:
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
.PHONY: test
|
|
55
|
+
test: ## Run the package test script when defined
|
|
56
|
+
@if grep -q '"test"' package.json 2>/dev/null; then $(NPM) test; \
|
|
57
|
+
else echo "$(COLOR_YELLOW)⚠ no test script — skipping$(COLOR_RESET)"; fi
|
|
48
58
|
|
|
49
59
|
.PHONY: clean
|
|
50
|
-
clean: ##
|
|
51
|
-
@rm -rf
|
|
52
|
-
@echo "$(COLOR_GREEN)✓
|
|
60
|
+
clean: ## Remove build artifacts
|
|
61
|
+
@rm -rf $(BUILD_DIR)
|
|
62
|
+
@echo "$(COLOR_GREEN)✓ cleaned $(BUILD_DIR)/$(COLOR_RESET)"
|
|
53
63
|
|
|
54
|
-
.PHONY:
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
.PHONY: build
|
|
65
|
+
build: ## Run the package build script when defined
|
|
66
|
+
@if grep -q '"build"' package.json 2>/dev/null; then $(NPM) run build; \
|
|
67
|
+
echo "$(COLOR_GREEN)✓ built$(COLOR_RESET)"; \
|
|
68
|
+
else echo "$(COLOR_YELLOW)⚠ no build script — nothing to build$(COLOR_RESET)"; fi
|
|
69
|
+
|
|
70
|
+
.PHONY: minify
|
|
71
|
+
minify: ## Minify assets under $(SRC_DIR) via the mokocli build agent when available
|
|
72
|
+
@MOKO=$(or $(wildcard ../mokocli),$(wildcard $(HOME)/mokocli),$(wildcard /opt/mokocli)); \
|
|
73
|
+
if [ -n "$$MOKO" ] && [ -f "$$MOKO/build/minify.js" ]; then $(NODE) "$$MOKO/build/minify.js" $(SRC_DIR); \
|
|
74
|
+
elif [ -f scripts/minify.js ]; then $(NODE) scripts/minify.js; \
|
|
75
|
+
else echo "$(COLOR_YELLOW)⚠ no minify script found$(COLOR_RESET)"; fi
|
|
57
76
|
|
|
58
|
-
.PHONY:
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
.PHONY: package
|
|
78
|
+
package: build ## Create the npm tarball (npm pack) + .sha256
|
|
79
|
+
@echo "$(COLOR_BLUE)Packaging $(PROJECT_NAME)-$(PROJECT_VERSION)...$(COLOR_RESET)"
|
|
80
|
+
@mkdir -p $(BUILD_DIR)
|
|
81
|
+
@$(NPM) pack --pack-destination $(BUILD_DIR) >/dev/null
|
|
82
|
+
@tgz=$$(ls -t $(BUILD_DIR)/*.tgz | head -1); \
|
|
83
|
+
sha256sum "$$tgz" | awk '{print $$1}' > "$$tgz.sha256"; \
|
|
84
|
+
echo "$(COLOR_GREEN)✓ packaged: $$tgz$(COLOR_RESET)"
|
|
61
85
|
|
|
62
|
-
.PHONY:
|
|
63
|
-
|
|
64
|
-
|
|
86
|
+
.PHONY: release
|
|
87
|
+
release: validate test package ## Validate, test, and package for release
|
|
88
|
+
@echo "$(COLOR_GREEN)✓ release artifacts ready for $(PROJECT_NAME) v$(PROJECT_VERSION)$(COLOR_RESET)"
|
|
65
89
|
|
|
66
|
-
.PHONY:
|
|
67
|
-
|
|
68
|
-
@echo "$(
|
|
90
|
+
.PHONY: version
|
|
91
|
+
version: ## Print the resolved version (Makefile is the source of truth)
|
|
92
|
+
@echo "$(PROJECT_NAME) $(PROJECT_VERSION) ($(PLATFORM))"
|
|
69
93
|
|
|
70
94
|
.DEFAULT_GOAL := help
|
package/dist/index.js
CHANGED
|
@@ -21,8 +21,6 @@ import { ApiClient } from './client.js';
|
|
|
21
21
|
import { hasRemoteControl } from './signing.js';
|
|
22
22
|
/** Base path for MokoSuiteClient Web Services (appended after /api/index.php). */
|
|
23
23
|
const MSC = '/v1/mokosuiteclient';
|
|
24
|
-
/** Base path for MokoSuiteExtensions (store) Web Services (appended after /api/index.php). */
|
|
25
|
-
const MSE = '/v1/mokosuiteextensions';
|
|
26
24
|
let config;
|
|
27
25
|
function clientFor(connection) {
|
|
28
26
|
return new ApiClient(getConnection(config, connection));
|
|
@@ -53,7 +51,7 @@ function requireConfirm(confirm) {
|
|
|
53
51
|
// ── Server ──────────────────────────────────────────────────────────────
|
|
54
52
|
const server = new McpServer({
|
|
55
53
|
name: 'mcp-mokosuite',
|
|
56
|
-
version: '1.
|
|
54
|
+
version: '1.0.0',
|
|
57
55
|
});
|
|
58
56
|
// ════════════════════════════════════════════════════════════════════════
|
|
59
57
|
// FOUNDATION — connectivity & generic access
|
|
@@ -170,31 +168,6 @@ server.tool('client_users_force_logout', 'Terminate ALL user sessions on a MokoS
|
|
|
170
168
|
return guard;
|
|
171
169
|
return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/force-logout`));
|
|
172
170
|
});
|
|
173
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
174
|
-
// LAYER 1 — Extensions Store (MokoSuiteExtensions)
|
|
175
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
176
|
-
server.tool('store_catalog_list', 'List the products available in the MokoSuite Extensions store catalog for a site.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSE}/catalog`)));
|
|
177
|
-
server.tool('store_catalog_get', 'Get a single MokoSuite Extensions store product by its catalog id.', {
|
|
178
|
-
id: z.number().describe('Catalog product id'),
|
|
179
|
-
...ConnectionParam,
|
|
180
|
-
}, async ({ id, connection }) => formatResponse(await clientFor(connection).get(`${MSE}/catalog/${id}`)));
|
|
181
|
-
server.tool('store_install', 'Install a MokoSuite Extensions store product on a site by its catalog id.', {
|
|
182
|
-
id: z.number().describe('Catalog product id to install'),
|
|
183
|
-
...ConnectionParam,
|
|
184
|
-
}, async ({ id, connection }) => formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/install`)));
|
|
185
|
-
server.tool('store_update', 'Update an installed MokoSuite Extensions store product on a site by its catalog id.', {
|
|
186
|
-
id: z.number().describe('Catalog product id to update'),
|
|
187
|
-
...ConnectionParam,
|
|
188
|
-
}, async ({ id, connection }) => formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/update`)));
|
|
189
|
-
server.tool('store_uninstall', 'Uninstall a MokoSuite Extensions store product from a site by its catalog id. Destructive — requires `confirm: true`.', {
|
|
190
|
-
id: z.number().describe('Catalog product id to uninstall'),
|
|
191
|
-
confirm: z.boolean().describe('Must be true — uninstalling removes the product from the target site'),
|
|
192
|
-
...ConnectionParam,
|
|
193
|
-
}, async ({ id, confirm, connection }) => {
|
|
194
|
-
if (!confirm)
|
|
195
|
-
return text('Refused: set `confirm: true` to uninstall this store product.');
|
|
196
|
-
return formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/uninstall`));
|
|
197
|
-
});
|
|
198
171
|
// ── Start Server ────────────────────────────────────────────────────────
|
|
199
172
|
async function main() {
|
|
200
173
|
config = await loadConfig();
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -25,9 +25,6 @@ import type { ApiConfig, ApiResponse } from './types.js';
|
|
|
25
25
|
/** Base path for MokoSuiteClient Web Services (appended after /api/index.php). */
|
|
26
26
|
const MSC = '/v1/mokosuiteclient';
|
|
27
27
|
|
|
28
|
-
/** Base path for MokoSuiteExtensions (store) Web Services (appended after /api/index.php). */
|
|
29
|
-
const MSE = '/v1/mokosuiteextensions';
|
|
30
|
-
|
|
31
28
|
let config: ApiConfig;
|
|
32
29
|
|
|
33
30
|
function clientFor(connection?: string): ApiClient {
|
|
@@ -67,7 +64,7 @@ function requireConfirm(confirm: boolean): { content: Array<{ type: 'text'; text
|
|
|
67
64
|
|
|
68
65
|
const server = new McpServer({
|
|
69
66
|
name: 'mcp-mokosuite',
|
|
70
|
-
version: '1.
|
|
67
|
+
version: '1.0.0',
|
|
71
68
|
});
|
|
72
69
|
|
|
73
70
|
// ════════════════════════════════════════════════════════════════════════
|
|
@@ -325,61 +322,6 @@ server.tool(
|
|
|
325
322
|
},
|
|
326
323
|
);
|
|
327
324
|
|
|
328
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
329
|
-
// LAYER 1 — Extensions Store (MokoSuiteExtensions)
|
|
330
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
331
|
-
|
|
332
|
-
server.tool(
|
|
333
|
-
'store_catalog_list',
|
|
334
|
-
'List the products available in the MokoSuite Extensions store catalog for a site.',
|
|
335
|
-
{ ...ConnectionParam },
|
|
336
|
-
async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSE}/catalog`)),
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
server.tool(
|
|
340
|
-
'store_catalog_get',
|
|
341
|
-
'Get a single MokoSuite Extensions store product by its catalog id.',
|
|
342
|
-
{
|
|
343
|
-
id: z.number().describe('Catalog product id'),
|
|
344
|
-
...ConnectionParam,
|
|
345
|
-
},
|
|
346
|
-
async ({ id, connection }) => formatResponse(await clientFor(connection).get(`${MSE}/catalog/${id}`)),
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
server.tool(
|
|
350
|
-
'store_install',
|
|
351
|
-
'Install a MokoSuite Extensions store product on a site by its catalog id.',
|
|
352
|
-
{
|
|
353
|
-
id: z.number().describe('Catalog product id to install'),
|
|
354
|
-
...ConnectionParam,
|
|
355
|
-
},
|
|
356
|
-
async ({ id, connection }) => formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/install`)),
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
server.tool(
|
|
360
|
-
'store_update',
|
|
361
|
-
'Update an installed MokoSuite Extensions store product on a site by its catalog id.',
|
|
362
|
-
{
|
|
363
|
-
id: z.number().describe('Catalog product id to update'),
|
|
364
|
-
...ConnectionParam,
|
|
365
|
-
},
|
|
366
|
-
async ({ id, connection }) => formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/update`)),
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
server.tool(
|
|
370
|
-
'store_uninstall',
|
|
371
|
-
'Uninstall a MokoSuite Extensions store product from a site by its catalog id. Destructive — requires `confirm: true`.',
|
|
372
|
-
{
|
|
373
|
-
id: z.number().describe('Catalog product id to uninstall'),
|
|
374
|
-
confirm: z.boolean().describe('Must be true — uninstalling removes the product from the target site'),
|
|
375
|
-
...ConnectionParam,
|
|
376
|
-
},
|
|
377
|
-
async ({ id, confirm, connection }) => {
|
|
378
|
-
if (!confirm) return text('Refused: set `confirm: true` to uninstall this store product.');
|
|
379
|
-
return formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/uninstall`));
|
|
380
|
-
},
|
|
381
|
-
);
|
|
382
|
-
|
|
383
325
|
// ── Start Server ────────────────────────────────────────────────────────
|
|
384
326
|
|
|
385
327
|
async function main(): Promise<void> {
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
-
#
|
|
5
|
-
# FILE INFORMATION
|
|
6
|
-
# DEFGROUP: MokoGIT.Workflow
|
|
7
|
-
# INGROUP: MokoCLI.Cascade
|
|
8
|
-
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
|
9
|
-
# PATH: /.mokogit/workflows/cascade-dev.yml
|
|
10
|
-
# VERSION: 02.04.00
|
|
11
|
-
# BRIEF: Cascade main -> dev; branch-motivated (no schedule); skips dev-only repos (no main); auto-merge clean, auto-resolve routine conflicts INLINE (manifest version / .mokogit-.gitea-.github / docs-stamps) with no mokocli dependency, fall back to cascade_resolve.php then notify
|
|
12
|
-
|
|
13
|
-
name: "Cascade Main -> Dev"
|
|
14
|
-
|
|
15
|
-
on:
|
|
16
|
-
# Branch-motivated (no time-based schedule): any real push to main cascades to
|
|
17
|
-
# dev immediately. Sync commits from workflow_sync.php carry [skip ci] and are
|
|
18
|
-
# skipped by the platform (they fire no trigger at all), so workflow_sync.php
|
|
19
|
-
# dispatches this workflow directly (workflow_dispatch) after it changes a repo
|
|
20
|
-
# — that is what carries .mokogit drift down to dev, replacing the old daily cron.
|
|
21
|
-
push:
|
|
22
|
-
branches:
|
|
23
|
-
- main
|
|
24
|
-
workflow_dispatch:
|
|
25
|
-
|
|
26
|
-
permissions:
|
|
27
|
-
contents: write
|
|
28
|
-
pull-requests: write
|
|
29
|
-
|
|
30
|
-
env:
|
|
31
|
-
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
|
|
32
|
-
# ntfy destination is configured via repo or org variables (org vars are inherited).
|
|
33
|
-
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
|
34
|
-
NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'git-releases' }}
|
|
35
|
-
|
|
36
|
-
jobs:
|
|
37
|
-
cascade:
|
|
38
|
-
name: Cascade main -> dev
|
|
39
|
-
runs-on: ubuntu-latest
|
|
40
|
-
steps:
|
|
41
|
-
- name: Checkout (full history for merge/resolve)
|
|
42
|
-
uses: actions/checkout@v4
|
|
43
|
-
with:
|
|
44
|
-
fetch-depth: 0
|
|
45
|
-
token: ${{ github.token }}
|
|
46
|
-
|
|
47
|
-
- name: Cascade main -> dev (auto-resolve routine conflicts, else notify)
|
|
48
|
-
env:
|
|
49
|
-
TOKEN: ${{ github.token }}
|
|
50
|
-
REPO: ${{ github.repository }}
|
|
51
|
-
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
|
52
|
-
run: |
|
|
53
|
-
set -uo pipefail
|
|
54
|
-
API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
|
|
55
|
-
AUTH="Authorization: token ${TOKEN}"
|
|
56
|
-
jqget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
|
57
|
-
|
|
58
|
-
# 0. dev must exist
|
|
59
|
-
if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then
|
|
60
|
-
echo "No dev branch - nothing to cascade."; exit 0
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# 0b. main must exist too. A dev-only repo (no main branch) has nothing to
|
|
64
|
-
# cascade FROM, so skip immediately instead of spinning up on the daily
|
|
65
|
-
# schedule / manual dispatch and no-op'ing later via a failed compare.
|
|
66
|
-
if ! curl -sf -H "$AUTH" "${API}/branches/main" >/dev/null 2>&1; then
|
|
67
|
-
echo "No main branch (dev-only repo) - nothing to cascade."; exit 0
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
# 1. is main ahead of dev?
|
|
71
|
-
AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
|
|
72
|
-
| python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
|
|
73
|
-
if [ "${AHEAD:-0}" -eq 0 ]; then
|
|
74
|
-
echo "dev already up to date with main."; exit 0
|
|
75
|
-
fi
|
|
76
|
-
echo "main is ${AHEAD} commit(s) ahead of dev."
|
|
77
|
-
|
|
78
|
-
# 2. reuse an open main->dev PR, else create one
|
|
79
|
-
PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \
|
|
80
|
-
| python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
|
|
81
|
-
if [ -z "$PR" ]; then
|
|
82
|
-
RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
|
|
83
|
-
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges when conflict-free, auto-resolves VERSION-stamp-only conflicts, otherwise left open for manual resolution."}')
|
|
84
|
-
PR=$(printf '%s' "$RESP" | jqget number)
|
|
85
|
-
if [ -z "$PR" ]; then
|
|
86
|
-
echo "::warning::Could not open cascade PR: $RESP"; exit 0
|
|
87
|
-
fi
|
|
88
|
-
echo "Opened cascade PR #${PR}"
|
|
89
|
-
else
|
|
90
|
-
echo "Reusing open cascade PR #${PR}"
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
notify() {
|
|
94
|
-
curl -sS \
|
|
95
|
-
-H "Title: ${REPO}: dev cascade needs manual merge" \
|
|
96
|
-
-H "Tags: warning,twisted_rightwards_arrows" \
|
|
97
|
-
-H "Priority: high" \
|
|
98
|
-
-H "Click: ${MOKOGIT_URL}/${REPO}/pulls/${PR}" \
|
|
99
|
-
-d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \
|
|
100
|
-
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# 3. wait for MokoGIT to compute mergeability (conflict detection)
|
|
104
|
-
MERGEABLE=""
|
|
105
|
-
for _ in 1 2 3 4 5 6; do
|
|
106
|
-
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqget mergeable)
|
|
107
|
-
case "$MERGEABLE" in True|False) break ;; esac
|
|
108
|
-
sleep 3
|
|
109
|
-
done
|
|
110
|
-
echo "mergeable=${MERGEABLE}"
|
|
111
|
-
|
|
112
|
-
# 4a. conflict-free -> merge via API (existing behaviour)
|
|
113
|
-
if [ "$MERGEABLE" = "True" ]; then
|
|
114
|
-
CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
|
|
115
|
-
-X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
|
|
116
|
-
if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
|
|
117
|
-
echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
|
|
118
|
-
exit 0
|
|
119
|
-
fi
|
|
120
|
-
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
|
|
121
|
-
notify "could not be auto-merged (HTTP ${CODE})."
|
|
122
|
-
exit 0
|
|
123
|
-
fi
|
|
124
|
-
|
|
125
|
-
# 4b. conflicts -> try to auto-resolve if they are ONLY VERSION-stamp lines.
|
|
126
|
-
echo "PR not cleanly mergeable; checking whether conflicts are VERSION-stamp-only..."
|
|
127
|
-
git config user.name "MokoGIT Cascade"
|
|
128
|
-
git config user.email "actions@mokoconsulting.tech"
|
|
129
|
-
git fetch --quiet origin main dev
|
|
130
|
-
git checkout -B dev origin/dev
|
|
131
|
-
|
|
132
|
-
if git merge --no-ff --no-commit origin/main >/dev/null 2>&1; then
|
|
133
|
-
# Became clean at git level (e.g. mergeability was still computing) -> commit + push.
|
|
134
|
-
git commit -m "chore(sync): cascade main -> dev [skip ci]" >/dev/null
|
|
135
|
-
git push origin dev
|
|
136
|
-
echo "Cascade merged cleanly at git level and pushed to dev."
|
|
137
|
-
exit 0
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
CONFLICTS=$(git diff --name-only --diff-filter=U)
|
|
141
|
-
echo "Conflicted files:"; echo "${CONFLICTS}"
|
|
142
|
-
|
|
143
|
-
# --- Inline resolver (pure bash/git, NO mokocli dependency) -------------
|
|
144
|
-
# The common cascade churn is routine and deterministic, so resolve it here
|
|
145
|
-
# BEFORE reaching for cascade_resolve.php. This is what keeps the version
|
|
146
|
-
# ladder moving even when mokocli is unreachable on an ephemeral runner
|
|
147
|
-
# (the failure mode this workflow was hardened against): a pure
|
|
148
|
-
# version-stamp conflict must never stall dev behind main.
|
|
149
|
-
#
|
|
150
|
-
# * manifest <version> conflicts (*.xml containing <extension) ->
|
|
151
|
-
# version = max(dev, main) via `sort -V`, rewriting the tag while
|
|
152
|
-
# preserving the file's own indentation (repos MIX tab and 2-space).
|
|
153
|
-
# * .mokogit/** .gitea/** .github/** -> take main/canonical (--theirs).
|
|
154
|
-
# * docs / CHANGELOG / stamp files -> keep dev (--ours).
|
|
155
|
-
# Anything still conflicted afterwards falls through to cascade_resolve.php
|
|
156
|
-
# (if available) and finally to a human, exactly as before.
|
|
157
|
-
resolve_manifest_version() {
|
|
158
|
-
# $1 = path to a conflicted *.xml manifest. Resolves ONLY the <version>
|
|
159
|
-
# conflict to max(dev,main); returns 0 if fully resolved, 1 otherwise.
|
|
160
|
-
local f="$1"
|
|
161
|
-
# Bail if the conflict touches anything other than a single <version> line
|
|
162
|
-
# on each side (i.e. a real content conflict, not just the stamp).
|
|
163
|
-
local nonver
|
|
164
|
-
nonver=$(awk '
|
|
165
|
-
/^<<<<<<< /{c=1;next} /^=======$/{c=2;next} /^>>>>>>> /{c=0;next}
|
|
166
|
-
(c==1||c==2) && $0 !~ /<version>[^<]*<\/version>/ {print "X"}
|
|
167
|
-
' "$f")
|
|
168
|
-
[ -n "$nonver" ] && return 1
|
|
169
|
-
# Collect the version value from each side of every conflict hunk.
|
|
170
|
-
local ours theirs maxv
|
|
171
|
-
ours=$(awk '
|
|
172
|
-
/^<<<<<<< /{c=1;next} /^=======$/{c=2;next} /^>>>>>>> /{c=0;next}
|
|
173
|
-
c==1' "$f" | sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' | head -n1)
|
|
174
|
-
theirs=$(awk '
|
|
175
|
-
/^<<<<<<< /{c=1;next} /^=======$/{c=2;next} /^>>>>>>> /{c=0;next}
|
|
176
|
-
c==2' "$f" | sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' | head -n1)
|
|
177
|
-
[ -z "$ours" ] && [ -z "$theirs" ] && return 1
|
|
178
|
-
maxv=$(printf '%s\n%s\n' "$ours" "$theirs" | grep -v '^$' | sort -V | tail -n1)
|
|
179
|
-
[ -z "$maxv" ] && return 1
|
|
180
|
-
# Rewrite: drop conflict markers, keep OUR side's <version> line but with
|
|
181
|
-
# the max value substituted (preserves this file's leading indentation,
|
|
182
|
-
# tab or spaces). Drop the theirs-side version line.
|
|
183
|
-
awk -v maxv="$maxv" '
|
|
184
|
-
/^<<<<<<< /{c=1;next}
|
|
185
|
-
/^=======$/{c=2;next}
|
|
186
|
-
/^>>>>>>> /{c=0;next}
|
|
187
|
-
{
|
|
188
|
-
if (c==2) next
|
|
189
|
-
if (($0 ~ /<version>[^<]*<\/version>/) && (c==1)) {
|
|
190
|
-
sub(/<version>[^<]*<\/version>/, "<version>" maxv "</version>")
|
|
191
|
-
}
|
|
192
|
-
print
|
|
193
|
-
}' "$f" > "${f}.resolved" && mv "${f}.resolved" "$f"
|
|
194
|
-
# Fully resolved only if no markers remain.
|
|
195
|
-
! grep -qE '^(<<<<<<< |=======$|>>>>>>> )' "$f"
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
for f in ${CONFLICTS}; do
|
|
199
|
-
case "$f" in
|
|
200
|
-
.mokogit/*|.gitea/*|.github/*)
|
|
201
|
-
git checkout --theirs -- "$f" && git add -- "$f" \
|
|
202
|
-
&& echo "resolved (theirs/main): $f" ;;
|
|
203
|
-
*CHANGELOG*|*/docs/*|docs/*|*.md|*VERSION*|*.stamp)
|
|
204
|
-
git checkout --ours -- "$f" && git add -- "$f" \
|
|
205
|
-
&& echo "resolved (ours/dev): $f" ;;
|
|
206
|
-
*.xml)
|
|
207
|
-
if grep -q '<extension' "$f" 2>/dev/null && resolve_manifest_version "$f"; then
|
|
208
|
-
git add -- "$f"; echo "resolved (manifest version=max): $f"
|
|
209
|
-
else
|
|
210
|
-
echo "unresolved (xml, not a simple version stamp): $f"
|
|
211
|
-
fi ;;
|
|
212
|
-
*)
|
|
213
|
-
echo "unresolved (needs deeper resolver): $f" ;;
|
|
214
|
-
esac
|
|
215
|
-
done
|
|
216
|
-
|
|
217
|
-
# If the inline pass cleared every conflict, commit + push and we are done —
|
|
218
|
-
# no mokocli needed.
|
|
219
|
-
if [ -z "$(git diff --name-only --diff-filter=U)" ]; then
|
|
220
|
-
git commit -m "chore(sync): cascade main -> dev (auto-resolved inline) [skip ci]" >/dev/null
|
|
221
|
-
git push origin dev
|
|
222
|
-
echo "Cascade auto-resolved inline (no mokocli) and pushed to dev."
|
|
223
|
-
curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
|
|
224
|
-
-d '{"body":"Cascade auto-resolved inline (manifest version=max(dev,main), .mokogit/.gitea/.github -> main, docs/stamps -> dev) with no mokocli dependency, and pushed to dev. Closing."}' >/dev/null || true
|
|
225
|
-
curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
|
|
226
|
-
-d '{"state":"closed"}' >/dev/null || true
|
|
227
|
-
exit 0
|
|
228
|
-
fi
|
|
229
|
-
|
|
230
|
-
echo "Inline pass left conflicts; falling back to cascade_resolve.php..."
|
|
231
|
-
echo "Still conflicted:"; git diff --name-only --diff-filter=U
|
|
232
|
-
|
|
233
|
-
# --- Fallback: cascade_resolve.php (existing behaviour) -----------------
|
|
234
|
-
# Handles the messier cases (modify/delete, plugin renames, etc.). It BAILS
|
|
235
|
-
# (exit 2) on real code conflicts, so those still get a human. ubuntu-latest
|
|
236
|
-
# has no /opt/mokocli, so clone mokocli on demand.
|
|
237
|
-
RESOLVER=/opt/mokocli/cli/cascade_resolve.php
|
|
238
|
-
if [ ! -f "$RESOLVER" ]; then
|
|
239
|
-
if git clone --depth 1 --quiet \
|
|
240
|
-
"https://x-access-token:${MOKOGIT_TOKEN}@${MOKOGIT_URL#https://}/MokoConsulting/mokocli.git" \
|
|
241
|
-
/tmp/mokocli 2>/dev/null \
|
|
242
|
-
|| git clone --depth 1 --quiet "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli 2>/dev/null; then
|
|
243
|
-
RESOLVER=/tmp/mokocli/cli/cascade_resolve.php
|
|
244
|
-
else
|
|
245
|
-
# Do NOT swallow this silently: a degraded run must be visible so the
|
|
246
|
-
# operator knows the fallback resolver was unavailable.
|
|
247
|
-
echo "::warning::On-demand mokocli clone failed; cascade_resolve.php is UNAVAILABLE. Inline resolver handled the routine classes; any remaining conflicts need a human."
|
|
248
|
-
RESOLVER=/tmp/mokocli/cli/cascade_resolve.php
|
|
249
|
-
fi
|
|
250
|
-
fi
|
|
251
|
-
|
|
252
|
-
if [ -f "$RESOLVER" ] && php "$RESOLVER" --path . --theirs-ref origin/main; then
|
|
253
|
-
git commit -m "chore(sync): cascade main -> dev (auto-resolved) [skip ci]" >/dev/null
|
|
254
|
-
git push origin dev
|
|
255
|
-
echo "Cascade auto-resolved and pushed to dev."
|
|
256
|
-
curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
|
|
257
|
-
-d '{"body":"cascade_resolve auto-resolved the merge (manifests/workflows/docs/stamps, modify-delete) and pushed to dev. Closing."}' >/dev/null || true
|
|
258
|
-
curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
|
|
259
|
-
-d '{"state":"closed"}' >/dev/null || true
|
|
260
|
-
exit 0
|
|
261
|
-
fi
|
|
262
|
-
|
|
263
|
-
# Resolver unavailable, or it bailed on a real (code) conflict -> human.
|
|
264
|
-
git merge --abort || true
|
|
265
|
-
notify "has code conflicts cascade_resolve could not safely auto-resolve."
|
|
266
|
-
exit 0
|