# .gitlab-ci.yml # # CI pipeline for ena-mcp-wasm. # # - test : smoke-test the MCP server on server.js / package.json changes # - trigger-cranqe: inherited from cranqe CI template; fires on main (dev build) # or a vX.Y.Z tag (release build) to publish @qe-libs/ena-mcp-wasm include: - project: 'epistemic-analytics/qe-packages/cranqe' ref: main file: '/ci-templates/trigger-cranqe.yml' stages: - test # ── Smoke test ───────────────────────────────────────────────────────────────── # Starts the MCP server, sends an initialize request, and confirms it responds # with a valid JSON-RPC result listing the expected tools. test: stage: test image: node:20-alpine rules: - changes: - server.js - package.json - package-lock.json script: - npm ci - | RESPONSE=$(echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}' \ | timeout 10 node server.js 2>/dev/null | head -1) echo "Response: ${RESPONSE}" echo "${RESPONSE}" | node -e " const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); if (!r.result?.serverInfo) { console.error('Missing serverInfo'); process.exit(1); } console.log('OK: server responded as', r.result.serverInfo.name, r.result.serverInfo.version); " - | TOOLS=$(printf '%s\n%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \ | timeout 10 node server.js 2>/dev/null | tail -1) echo "Tools response: ${TOOLS}" echo "${TOOLS}" | node -e " const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); const names = (r.result?.tools ?? []).map(t => t.name); const expected = ['ena_profile','ena_inspect','ena_fit','ena_accumulate','ena_compare_groups','ena_plot']; const missing = expected.filter(n => !names.includes(n)); if (missing.length) { console.error('Missing tools:', missing.join(', ')); process.exit(1); } console.log('OK: tools present —', names.join(', ')); " # ── cranqe deployment ────────────────────────────────────────────────────────── # verify-tag-version is overridden to read from package.json (not R's DESCRIPTION). # trigger-cranqe and trigger-cranqe-release are inherited from the template. verify-tag-version: script: - | TAG_VERSION="${CI_COMMIT_TAG#v}" # Parse with sed — the template's alpine image has no node PKG_VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' package.json | head -1) if [ "${TAG_VERSION}" != "${PKG_VERSION}" ]; then echo "-----------------------------------------------------------" echo " Tag/package.json mismatch — release blocked" echo "-----------------------------------------------------------" echo " Tag: ${CI_COMMIT_TAG} (version ${TAG_VERSION})" echo " package.json: ${PKG_VERSION}" echo "" echo " Fix: update package.json version to ${TAG_VERSION}" echo " then commit, push, and re-tag." echo "-----------------------------------------------------------" exit 1 fi echo "-----------------------------------------------------------" echo " OK: ${CI_COMMIT_TAG} matches package.json ${PKG_VERSION}" echo "-----------------------------------------------------------" trigger-cranqe: rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH changes: - server.js - package.json - package-lock.json - hooks/**/* - README.md